Compare commits

...

44 Commits

Author SHA1 Message Date
github-actions[bot]
40ce6603b3 🎨 Auto format 2026-01-16 12:11:55 +00:00
github-actions[bot]
a9ca408f6a 🌐 Update translations for fr (update-outdated) 2026-01-16 12:11:23 +00:00
github-actions[bot]
8fa635c718 📝 Update release notes
[skip ci]
2026-01-16 11:57:32 +00:00
Sebastián Ramírez
fb15bba819 🌐 Update LLM prompt instructions file for French (#14618) 2026-01-16 12:57:08 +01:00
github-actions[bot]
23bcfa094d 📝 Update release notes
[skip ci]
2026-01-16 11:54:26 +00:00
Sebastián Ramírez
f317ede223 🌐 Update translations for ko (add-missing) (#14699)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-16 12:54:01 +01:00
github-actions[bot]
c597d9cb53 📝 Update release notes
[skip ci]
2026-01-16 10:54:08 +00:00
fcharrier
a96dd013a4 🐛 Fix copy button in custom.js (#14722) 2026-01-16 10:53:45 +00:00
github-actions[bot]
a456e92a21 📝 Update release notes
[skip ci]
2026-01-11 22:23:21 +00:00
Sebastián Ramírez
1be80f4885 📝 Add contribution instructions about LLM generated code and comments and automated tools for PRs (#14706) 2026-01-11 23:22:58 +01:00
github-actions[bot]
e63f382b0f 📝 Update release notes
[skip ci]
2026-01-11 21:19:50 +00:00
Sebastián Ramírez
7b864acf37 📝 Update docs for management tasks (#14705) 2026-01-11 21:19:26 +00:00
github-actions[bot]
e9e0419ed0 📝 Update release notes
[skip ci]
2026-01-11 19:19:29 +00:00
Sebastián Ramírez
249a776b70 📝 Update docs about managing translations (#14704) 2026-01-11 19:19:05 +00:00
github-actions[bot]
97aa825422 📝 Update release notes
[skip ci]
2026-01-11 18:18:58 +00:00
Sebastián Ramírez
1054fbd256 📝 Update docs for contributing with translations (#14701) 2026-01-11 18:18:38 +00:00
github-actions[bot]
effe493ae0 📝 Update release notes
[skip ci]
2026-01-11 16:43:35 +00:00
github-actions[bot]
612a2d20bc 📝 Update release notes
[skip ci]
2026-01-11 16:43:25 +00:00
dependabot[bot]
14f3068762 ⬆ Bump actions/cache from 4 to 5 (#14511)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 17:42:51 +01:00
dependabot[bot]
d05b18ec40 ⬆ Bump actions/upload-artifact from 5 to 6 (#14525)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 17:42:37 +01:00
github-actions[bot]
9a76f2fec9 📝 Update release notes
[skip ci]
2026-01-11 16:42:08 +00:00
dependabot[bot]
0383fb3ab9 ⬆ Bump actions/download-artifact from 6 to 7 (#14526)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 17:41:45 +01:00
github-actions[bot]
50fa3f7c88 📝 Update release notes
[skip ci]
2026-01-11 00:21:31 +00:00
Sebastián Ramírez
5ec2615b1a 👷 Tweak CI input names (#14688) 2026-01-11 01:21:07 +01:00
github-actions[bot]
16e583413c 📝 Update release notes
[skip ci]
2026-01-11 00:16:33 +00:00
github-actions[bot]
1fedd1c73b 📝 Update release notes
[skip ci]
2026-01-11 00:15:31 +00:00
Sebastián Ramírez
cf8dc98aad 🌐 Update translations for ko (update-outdated) (#14589)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-01-11 00:15:26 +00:00
Sebastián Ramírez
6f977366a4 🌐 Update translations for uk (update-outdated) (#14587)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-01-11 01:15:06 +01:00
github-actions[bot]
154ce03ff0 📝 Update release notes
[skip ci]
2026-01-11 00:04:10 +00:00
Sebastián Ramírez
49653aa295 🔨 Refactor translation script to allow committing in place (#14687) 2026-01-11 00:03:50 +00:00
github-actions[bot]
f03a1502a0 📝 Update release notes
[skip ci]
2026-01-10 23:41:44 +00:00
Sebastián Ramírez
a2912ffa26 🌐 Update translations for es (update-outdated) (#14686)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-11 00:41:20 +01:00
github-actions[bot]
8183e748ee 📝 Update release notes
[skip ci]
2026-01-10 23:06:59 +00:00
Sebastián Ramírez
cefd50702a 🐛 Fix translation script path (#14685) 2026-01-10 23:06:37 +00:00
github-actions[bot]
3d1f9268fc 📝 Update release notes
[skip ci]
2026-01-10 22:44:05 +00:00
Sebastián Ramírez
7eac6e3169 Enable tests in CI for scripts (#14684) 2026-01-10 23:43:44 +01:00
github-actions[bot]
21d2c5cea0 📝 Update release notes
[skip ci]
2026-01-10 22:18:13 +00:00
Sebastián Ramírez
c75ae058e4 🔧 Add pre-commit local script to fix language translations (#14683) 2026-01-10 22:17:46 +00:00
github-actions[bot]
961b2e844a 📝 Update release notes
[skip ci]
2026-01-10 22:03:19 +00:00
Jonathan Ehwald
b4ba7f4652 ⬆️ Migrate to uv (#14676) 2026-01-10 23:02:57 +01:00
github-actions[bot]
c35e1fd4b4 📝 Update release notes
[skip ci]
2026-01-10 21:48:32 +00:00
Motov Yurii
d1c67c0055 🔨 Add LLM translations tool fixer (#14652)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-01-10 21:48:08 +00:00
github-actions[bot]
18762e38a9 📝 Update release notes
[skip ci]
2026-01-10 21:35:32 +00:00
Motov Yurii
b1db1395b6 📝 Specify language code for code block (#14656) 2026-01-10 22:35:09 +01:00
263 changed files with 22745 additions and 7226 deletions

View File

@@ -8,7 +8,7 @@ updates:
commit-message:
prefix:
# Python
- package-ecosystem: "pip"
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "monthly"

View File

@@ -8,9 +8,6 @@ on:
- opened
- synchronize
env:
UV_SYSTEM_PYTHON: 1
jobs:
changes:
runs-on: ubuntu-latest
@@ -31,8 +28,8 @@ jobs:
- README.md
- docs/**
- docs_src/**
- requirements-docs.txt
- pyproject.toml
- uv.lock
- mkdocs.yml
- mkdocs.env.yml
- .github/workflows/build-docs.yml
@@ -49,21 +46,20 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install docs extras
run: uv pip install -r requirements-docs.txt
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:
@@ -83,26 +79,25 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install docs extras
run: uv pip install -r requirements-docs.txt
run: uv sync --locked --no-dev --group docs
- name: Update Languages
run: python ./scripts/docs.py update-languages
- uses: actions/cache@v4
run: uv run ./scripts/docs.py update-languages
- uses: actions/cache@v5
with:
key: mkdocs-cards-${{ matrix.lang }}-${{ github.ref }}
path: docs/${{ matrix.lang }}/.cache
- name: Build Docs
run: python ./scripts/docs.py build-lang ${{ matrix.lang }}
- uses: actions/upload-artifact@v5
run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }}
- uses: actions/upload-artifact@v6
with:
name: docs-site-${{ matrix.lang }}
path: ./site/**

View File

@@ -10,9 +10,6 @@ on:
required: false
default: "false"
env:
UV_SYSTEM_PYTHON: 1
jobs:
job:
if: github.repository_owner == 'fastapi'
@@ -28,17 +25,16 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -48,6 +44,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}
- name: FastAPI People Contributors
run: python ./scripts/contributors.py
run: uv run ./scripts/contributors.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}

View File

@@ -12,9 +12,6 @@ permissions:
pull-requests: write
statuses: write
env:
UV_SYSTEM_PYTHON: 1
jobs:
deploy-docs:
runs-on: ubuntu-latest
@@ -27,19 +24,18 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install GitHub Actions dependencies
run: uv pip install -r requirements-github-actions.txt
run: uv sync --locked --no-dev --group github-actions
- name: Deploy Docs Status Pending
run: python ./scripts/deploy_docs_status.py
run: uv run ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
@@ -49,7 +45,7 @@ jobs:
run: |
rm -rf ./site
mkdir ./site
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
with:
path: ./site/
pattern: docs-site-*
@@ -70,14 +66,14 @@ jobs:
command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }}
- name: Deploy Docs Status Error
if: failure()
run: python ./scripts/deploy_docs_status.py
run: uv run ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_ID: ${{ github.run_id }}
STATE: "error"
- name: Comment Deploy
run: python ./scripts/deploy_docs_status.py
run: uv run ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}

View File

@@ -41,11 +41,15 @@ jobs:
"message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.",
"reminder": {
"before": "P3D",
"message": "Heads-up: this will be closed in 3 days unless theres new activity."
"message": "Heads-up: this will be closed in 3 days unless there's new activity."
}
},
"invalid": {
"delay": 0,
"message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details."
},
"maybe-ai": {
"delay": 0,
"message": "This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI."
}
}

View File

@@ -8,9 +8,6 @@ on:
permissions:
pull-requests: write
env:
UV_SYSTEM_PYTHON: 1
jobs:
label-approved:
if: github.repository_owner == 'fastapi'
@@ -24,19 +21,18 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install GitHub Actions dependencies
run: uv pip install -r requirements-github-actions.txt
run: uv sync --locked --no-dev --group github-actions
- name: Label Approved
run: python ./scripts/label_approved.py
run: uv run ./scripts/label_approved.py
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG: >

View File

@@ -15,9 +15,6 @@ on:
required: false
default: 'false'
env:
UV_SYSTEM_PYTHON: 1
jobs:
job:
runs-on: ubuntu-latest
@@ -32,17 +29,16 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -52,7 +48,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Notify Translations
run: python ./scripts/notify_translations.py
run: uv run ./scripts/notify_translations.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUMBER: ${{ github.event.inputs.number || null }}

View File

@@ -10,9 +10,6 @@ on:
required: false
default: "false"
env:
UV_SYSTEM_PYTHON: 1
jobs:
job:
if: github.repository_owner == 'fastapi'
@@ -28,17 +25,16 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -48,7 +44,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}
- name: FastAPI People Experts
run: python ./scripts/people.py
run: uv run ./scripts/people.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}
SLEEP_INTERVAL: ${{ vars.PEOPLE_SLEEP_INTERVAL }}

View File

@@ -40,18 +40,15 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.14"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install Dependencies
run: |
uv venv
uv pip install -r requirements.txt
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

View File

@@ -15,6 +15,7 @@ jobs:
- fastapi-slim
permissions:
id-token: write
contents: read
steps:
- name: Dump GitHub context
env:
@@ -24,19 +25,15 @@ jobs:
- 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

View File

@@ -8,9 +8,6 @@ on:
permissions:
statuses: write
env:
UV_SYSTEM_PYTHON: 1
jobs:
smokeshow:
runs-on: ubuntu-latest
@@ -23,15 +20,15 @@ jobs:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.13'
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- run: uv pip install -r requirements-github-actions.txt
- uses: actions/download-artifact@v6
uv.lock
- run: uv sync --locked --no-dev --group github-actions
- uses: actions/download-artifact@v7
with:
name: coverage-html
path: htmlcov
@@ -41,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

View File

@@ -10,9 +10,6 @@ on:
required: false
default: "false"
env:
UV_SYSTEM_PYTHON: 1
jobs:
job:
if: github.repository_owner == 'fastapi'
@@ -28,17 +25,16 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
@@ -46,7 +42,7 @@ jobs:
with:
limit-access-to-actor: true
- name: FastAPI People Sponsors
run: python ./scripts/sponsors.py
run: uv run ./scripts/sponsors.py
env:
SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}

View File

@@ -26,7 +26,7 @@ jobs:
- 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

View File

@@ -13,7 +13,7 @@ on:
- cron: "0 0 * * 1"
env:
UV_SYSTEM_PYTHON: 1
UV_NO_SYNC: true
jobs:
test:
@@ -44,6 +44,8 @@ jobs:
coverage: coverage
fail-fast: false
runs-on: ${{ matrix.os }}
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- name: Dump GitHub context
env:
@@ -57,17 +59,16 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv pip install -r requirements-tests.txt
run: uv sync --locked --no-dev --group tests --extra all
- run: mkdir coverage
- name: Test
if: matrix.codspeed != 'codspeed'
run: bash scripts/test.sh
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 }}
@@ -79,11 +80,11 @@ jobs:
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
with:
mode: simulation
run: coverage run -m pytest tests/ --codspeed
run: uv run coverage run -m pytest tests/ --codspeed
# Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow
- name: Store coverage files
if: matrix.coverage == 'coverage'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }}
path: coverage
@@ -100,33 +101,32 @@ jobs:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv pip install -r requirements-tests.txt
run: uv sync --locked --no-dev --group tests --extra all
- name: Get coverage files
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
pattern: coverage-*
path: coverage
merge-multiple: true
- run: ls -la coverage
- run: coverage combine coverage
- run: coverage html --title "Coverage for ${{ github.sha }}"
- run: uv run coverage combine coverage
- run: uv run coverage html --title "Coverage for ${{ github.sha }}"
- name: Store coverage HTML
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: coverage-html
path: htmlcov
include-hidden-files: true
- run: coverage report --fail-under=100
- 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

View File

@@ -5,9 +5,6 @@ on:
- cron: "0 12 1 * *"
workflow_dispatch:
env:
UV_SYSTEM_PYTHON: 1
jobs:
topic-repos:
if: github.repository_owner == 'fastapi'
@@ -23,18 +20,17 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
uv.lock
- name: Install GitHub Actions dependencies
run: uv pip install -r requirements-github-actions.txt
run: uv sync --locked --no-dev --group github-actions
- name: Update Topic Repos
run: python ./scripts/topic_repos.py
run: uv run ./scripts/topic_repos.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}

View File

@@ -30,9 +30,11 @@ 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:
langs:
@@ -45,20 +47,20 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
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
- name: Export Language Codes
id: show-langs
run: |
echo "langs=$(python ./scripts/translate.py llm-translatable-json)" >> $GITHUB_OUTPUT
echo "commands=$(python ./scripts/translate.py commands-json)" >> $GITHUB_OUTPUT
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 }}
@@ -84,15 +86,15 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
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
@@ -104,11 +106,12 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: FastAPI Translate
run: |
python ./scripts/translate.py ${{ matrix.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: ${{ matrix.lang }}
EN_PATH: ${{ github.event.inputs.en_path }}
COMMAND: ${{ matrix.command }}
COMMIT_IN_PLACE: ${{ github.event.inputs.commit_in_place }}

3
.gitignore vendored
View File

@@ -29,7 +29,4 @@ archive.zip
# macOS
.DS_Store
# Ignore while the setup still depends on requirements.txt files
uv.lock
.codspeed

View File

@@ -6,6 +6,7 @@ repos:
hooks:
- id: check-added-large-files
args: ['--maxkb=750']
exclude: ^uv.lock$
- id: check-toml
- id: check-yaml
args:
@@ -57,3 +58,9 @@ repos:
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$

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.11

View File

@@ -6,44 +6,20 @@ First, you might want to see the basic ways to [help FastAPI and get help](help-
If you already cloned the <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">fastapi repository</a> and you want to deep dive in the code, here are some guidelines to set up your environment.
### Virtual environment
Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `fastapi`.
### Install requirements
After activating the environment, install the required packages:
//// tab | `pip`
Create a virtual environment and install the required packages with <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
<div class="termy">
```console
$ pip install -r requirements.txt
$ uv sync
---> 100%
```
</div>
////
//// tab | `uv`
If you have <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
<div class="termy">
```console
$ uv pip install -r requirements.txt
---> 100%
```
</div>
////
It will install all the dependencies and your local FastAPI in your local environment.
### Using your local FastAPI
@@ -56,9 +32,9 @@ That way, you don't have to "install" your local version to be able to test ever
/// note | Technical Details
This only happens when you install using this included `requirements.txt` instead of running `pip install fastapi` directly.
This only happens when you install using `uv sync` instead of running `pip install fastapi` directly.
That is because inside the `requirements.txt` file, the local version of FastAPI is marked to be installed in "editable" mode, with the `-e` option.
That is because `uv sync` will install the local version of FastAPI in "editable" mode by default.
///
@@ -201,252 +177,81 @@ as Uvicorn by default will use the port `8000`, the documentation on port `8008`
### Translations
/// warning | Attention
**Update on Translations**
We're updating the way we handle documentation translations.
Until now, we invited community members to translate pages via pull requests, which were then reviewed by at least two native speakers. While this has helped bring FastAPI to many more users, weve also run into several challenges - some languages have only a few translated pages, others are outdated and hard to maintain over time.
To improve this, were working on automation tools 🤖 to manage translations more efficiently. Once ready, documentation will be machine-translated and still reviewed by at least two native speakers ✅ before publishing. This will allow us to keep translations up-to-date while reducing the review burden on maintainers.
Whats changing now:
* 🚫 Were no longer accepting new community-submitted translation PRs.
* ⏳ Existing open PRs will be reviewed and can still be merged if completed within the next 3 weeks (since July 11 2025).
* 🌐 In the future, we will only support languages where at least three active native speakers are available to review and maintain translations.
This transition will help us keep translations more consistent and timely while better supporting our contributors 🙌. Thank you to everyone who has contributed so far — your help has been invaluable! 💖
///
Help with translations is VERY MUCH appreciated! And it can't be done without the help from the community. 🌎 🚀
Here are the steps to help with translations.
#### Tips and guidelines
#### Review Translation PRs
Translation pull requests are made by LLMs guided with prompts designed by the FastAPI team together with the community of native speakers for each supported language.
These translations are normally still reviewed by native speakers, and here's where you can help!
* Check the currently <a href="https://github.com/fastapi/fastapi/pulls" class="external-link" target="_blank">existing pull requests</a> for your language. You can filter the pull requests by the ones with the label for your language. For example, for Spanish, the label is <a href="https://github.com/fastapi/fastapi/pulls?q=is%3Aopen+sort%3Aupdated-desc+label%3Alang-es+label%3Aawaiting-review" class="external-link" target="_blank">`lang-es`</a>.
* Review those pull requests, requesting changes or approving them. For the languages I don't speak, I'll wait for several others to review the translation before merging.
* When reviewing a pull request, it's better not to suggest changes in the same pull request, because it is LLM generated, and it won't be possible to make sure that small individual changes are replicated in other similar sections, or that they are preserved when translating the same content again.
* Instead of adding suggestions to the translation PR, make the suggestions to the LLM prompt file for that language, in a new PR. For example, for Spanish, the LLM prompt file is at: <a href="https://github.com/fastapi/fastapi/blob/master/docs/es/llm-prompt.md" class="external-link" target="_blank">`docs/es/llm-prompt.md`</a>.
/// tip
You can <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request" class="external-link" target="_blank">add comments with change suggestions</a> to existing pull requests.
Check the docs about <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-reviews" class="external-link" target="_blank">adding a pull request review</a> to approve it or request changes.
///
#### Subscribe to Notifications for Your Language
* Check if there's a <a href="https://github.com/fastapi/fastapi/discussions/categories/translations" class="external-link" target="_blank">GitHub Discussion</a> to coordinate translations for your language. You can subscribe to it, and when there's a new pull request to review, an automatic comment will be added to the discussion.
* If you translate pages, add a single pull request per page translated. That will make it much easier for others to review it.
* To check the 2-letter code for the language you want to translate, you can use the table <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" class="external-link" target="_blank">List of ISO 639-1 codes</a>.
#### Existing language
Let's say you want to translate a page for a language that already has translations for some pages, like Spanish.
In the case of Spanish, the 2-letter code is `es`. So, the directory for Spanish translations is located at `docs/es/`.
/// tip
The main ("official") language is English, located at `docs/en/`.
///
Now run the live server for the docs in Spanish:
<div class="termy">
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
</div>
/// tip
Alternatively, you can perform the same steps that scripts does manually.
Go into the language directory, for the Spanish translations it's at `docs/es/`:
```console
$ cd docs/es/
```
Then run `mkdocs` in that directory:
```console
$ mkdocs serve --dev-addr 127.0.0.1:8008
```
///
Now you can go to <a href="http://127.0.0.1:8008" class="external-link" target="_blank">http://127.0.0.1:8008</a> and see your changes live.
You will see that every language has all the pages. But some pages are not translated and have an info box at the top, about the missing translation.
Now let's say that you want to add a translation for the section [Features](features.md){.internal-link target=_blank}.
* Copy the file at:
```
docs/en/docs/features.md
```
* Paste it in exactly the same location but for the language you want to translate, e.g.:
```
docs/es/docs/features.md
```
/// tip
Notice that the only change in the path and file name is the language code, from `en` to `es`.
///
If you go to your browser you will see that now the docs show your new section (the info box at the top is gone). 🎉
Now you can translate it all and see how it looks as you save the file.
#### Don't Translate these Pages
🚨 Don't translate:
* Files under `reference/`
* `release-notes.md`
* `fastapi-people.md`
* `external-links.md`
* `newsletter.md`
* `management-tasks.md`
* `management.md`
* `contributing.md`
Some of these files are updated very frequently and a translation would always be behind, or they include the main content from English source files, etc.
#### Request a New Language
Let's say that you want to request translations for a language that is not yet translated, not even some pages. For example, Latin.
If there is no discussion for that language, you can start by requesting the new language. For that, you can follow these steps:
* The first step would be for you to find other 2 people that would be willing to be reviewing translation PRs for that language with you.
* Once there are at least 3 people that would be willing to commit to help maintain that language, you can continue the next steps.
* Create a new discussion following the template.
* Get a few native speakers to comment on the discussion and commit to help review translations for that language.
* Tag the other 2 people that will help with the language, and ask them to confirm there they will help.
Once there are several people in the discussion, the FastAPI team can evaluate it and can make it an official translation.
Then the docs will be automatically translated using AI, and the team of native speakers can review the translation, and help tweak the AI prompts.
Then the docs will be automatically translated using LLMs, and the team of native speakers can review the translation, and help tweak the LLM prompts.
Once there's a new translation, for example if docs are updated or there's a new section, there will be a comment in the same discussion with the link to the new translation to review.
#### New Language
## Automated Code and AI
/// note
You are encouraged to use all the tools you want to do your work and contribute as efficiently as possible, this includes AI (LLM) tools, etc. Nevertheless, contributions should have meaningful human intervention, judgement, context, etc.
These steps will be performed by the FastAPI team.
If the **human effort** put in a PR, e.g. writing LLM prompts, is **less** than the **effort we would need to put** to **review it**, please **don't** submit the PR.
///
Think of it this way: we can already write LLM prompts or run automated tools ourselves, and that would be faster than reviewing external PRs.
Checking the link from above (List of ISO 639-1 codes), you can see that the 2-letter code for Latin is `la`.
### Closing Automated and AI PRs
Now you can create a new directory for the new language, running the following script:
If we see PRs that seem AI generated or automated in similar ways, we'll flag them and close them.
<div class="termy">
The same applies to comments and descriptions, please don't copy paste the content generated by an LLM.
```console
// Use the command new-lang, pass the language code as a CLI argument
$ python ./scripts/docs.py new-lang la
### Human Effort Denial of Service
Successfully initialized: docs/la
```
Using automated tools and AI to submit PRs or comments that we have to carefully review and handle would be the equivalent of a <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack" class="external-link" target="_blank">Denial-of-service attack</a> on our human effort.
</div>
It would be very little effort from the person submitting the PR (an LLM prompt) that generates a large amount of effort on our side (carefully reviewing code).
Now you can check in your code editor the newly created directory `docs/la/`.
Please don't do that.
That command created a file `docs/la/mkdocs.yml` with a simple config that inherits everything from the `en` version:
We'll need to block accounts that spam us with repeated automated PRs or comments.
```yaml
INHERIT: ../en/mkdocs.yml
```
### Use Tools Wisely
/// tip
As Uncle Ben said:
You could also simply create that file with those contents manually.
<blockquote>
With great <strike>power</strike> <strong>tools</strong> comes great responsibility.
</blockquote>
///
Avoid inadvertently doing harm.
That command also created a dummy file `docs/la/index.md` for the main page, you can start by translating that one.
You can continue with the previous instructions for an "Existing Language" for that process.
You can make the first pull request with those two files, `docs/la/mkdocs.yml` and `docs/la/index.md`. 🎉
#### Preview the result
As already mentioned above, you can use the `./scripts/docs.py` with the `live` command to preview the results (or `mkdocs serve`).
Once you are done, you can also test it all as it would look online, including all the other languages.
To do that, first build all the docs:
<div class="termy">
```console
// Use the command "build-all", this will take a bit
$ python ./scripts/docs.py build-all
Building docs for: en
Building docs for: es
Successfully built docs for: es
```
</div>
This builds all those independent MkDocs sites for each language, combines them, and generates the final output at `./site/`.
Then you can serve that with the command `serve`:
<div class="termy">
```console
// Use the command "serve" after running "build-all"
$ python ./scripts/docs.py serve
Warning: this is a very simple server. For development, use mkdocs serve instead.
This is here only to preview a site with translations already built.
Make sure you run the build-all command first.
Serving at: http://127.0.0.1:8008
```
</div>
#### Translation specific tips and guidelines
* Translate only the Markdown documents (`.md`). Do not translate the code examples at `./docs_src`.
* In code blocks within the Markdown document, translate comments (`# a comment`), but leave the rest unchanged.
* Do not change anything enclosed in "``" (inline code).
* In lines starting with `///` translate only the text part after `|`. Leave the rest unchanged.
* You can translate info boxes like `/// warning` with for example `/// warning | Achtung`. But do not change the word immediately after the `///`, it determines the color of the info box.
* Do not change the paths in links to images, code files, Markdown documents.
* 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.

View File

@@ -196,31 +196,11 @@ They have contributed source code, documentation, etc. 📦
There are hundreds of other contributors, you can see them all in the <a href="https://github.com/fastapi/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷
## Top Translators
These are the **Top Translators**. 🌐
These users have created the most Pull Requests with [translations to other languages](contributing.md#translations){.internal-link target=_blank} that have been *merged*.
<div class="user-list user-list-center">
{% for user in (translators.values() | list)[:50] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Translations: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
## Top Translation Reviewers
These users are the **Top Translation Reviewers**. 🕵️
I only speak a few languages (and not very well 😅). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
Translation reviewers have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
<div class="user-list user-list-center">
{% for user in (translation_reviewers.values() | list)[:50] %}

View File

@@ -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,

View File

@@ -74,7 +74,7 @@ Make sure you use a supported label from the <a href="https://github.com/tiangol
* `refactor`: Refactors
* This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc.
* `upgrade`: Upgrades
* This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies, normally in `requirements.txt` files or GitHub Action versions should be marked as `internal`, not `upgrade`.
* This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies or GitHub Action versions should be marked as `internal`, not `upgrade`.
* `docs`: Docs
* Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations.
* You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`.
@@ -106,135 +106,25 @@ This way, we can notice when there are new translations ready, because they have
## Merge Translation PRs
For Spanish, as I'm a native speaker and it's a language close to me, I will give it a final review myself and in most cases tweak the PR a bit before merging it.
Translations are generated automatically with LLMs and scripts.
For the other languages, confirm that:
There's one GitHub Action that can be manually run to add or update translations for a language: <a href="https://github.com/fastapi/fastapi/actions/workflows/translate.yml" class="external-link" target="_blank">`translate.yml`</a>.
* The title is correct following the instructions above.
For these language translation PRs, confirm that:
* The PR was automated (authored by @tiangolo), not made by another user.
* 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:
```
/// 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 <a href="https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes" class="external-link" target="_blank">ISO 639-1 list of language codes</a>.
In any case, the language code should be in the file <a href="https://github.com/fastapi/fastapi/blob/master/docs/language_names.yml" class="external-link" target="_blank">docs/language_names.yml</a>.
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 <a href="https://github.com/fastapi/fastapi/discussions/categories/translations" class="external-link" target="_blank">Translations GitHub Discussions</a>
* 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`.
Then go back to the PR, and add the label, like `lang-bs`, and `lang-all` and `awaiting-review`.
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 the PR is approved by at least one native speaker, you can merge it.
## Review PRs
If a PR doesn't explain what it does or why, ask for more information.
* 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.
A PR should have a specific use case that it is solving.
* 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 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.
* 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.
@@ -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 <a href="https://github.com/fastapi/fastapi/blob/master/docs/en/data/external_links.yml" class="external-link" target="_blank">external_links.yml</a>.
* 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

View File

@@ -7,14 +7,38 @@ 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 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
* ⬆ 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).

View File

@@ -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"

View File

@@ -317,10 +317,14 @@ extra:
name: de - Deutsch
- link: /es/
name: es - español
- link: /ko/
name: ko - 한국어
- link: /pt/
name: pt - português
- link: /ru/
name: ru - русский язык
- link: /uk/
name: uk - українська мова
extra_css:
- css/termynal.css
- css/custom.css

View File

@@ -1,17 +1,17 @@
# Archivo de prueba de LLM { #llm-test-file }
Este documento prueba si el <abbr title="Large Language Model Modelo de lenguaje grande">LLM</abbr>, que traduce la documentación, entiende el `general_prompt` en `scripts/translate.py` y el prompt específico del idioma en `docs/{language code}/llm-prompt.md`. El prompt específico del idioma se agrega al final de `general_prompt`.
Este documento prueba si el <abbr title="Large Language Model - Modelo de lenguaje grande">LLM</abbr>, que traduce la documentación, entiende el `general_prompt` en `scripts/translate.py` y el prompt específico del idioma en `docs/{language code}/llm-prompt.md`. El prompt específico del idioma se agrega al final de `general_prompt`.
Las pruebas añadidas aquí serán vistas por todas las personas que diseñan prompts específicos del idioma.
Úsalo de la siguiente manera:
* Ten un prompt específico del idioma `docs/{language code}/llm-prompt.md`.
* Ten un prompt específico del idioma - `docs/{language code}/llm-prompt.md`.
* Haz una traducción fresca de este documento a tu idioma destino (mira, por ejemplo, el comando `translate-page` de `translate.py`). Esto creará la traducción en `docs/{language code}/docs/_llm-test.md`.
* Comprueba si todo está bien en la traducción.
* Revisa si las cosas están bien en la traducción.
* Si es necesario, mejora tu prompt específico del idioma, el prompt general, o el documento en inglés.
* Luego corrige manualmente los problemas restantes en la traducción para que sea una buena traducción.
* Vuelve a traducir, teniendo la buena traducción en su lugar. El resultado ideal sería que el LLM ya no hiciera cambios a la traducción. Eso significa que el prompt general y tu prompt específico del idioma están tan bien como pueden estar (a veces hará algunos cambios aparentemente aleatorios; la razón es que <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">los LLMs no son algoritmos deterministas</a>).
* Vuelve a traducir, teniendo la buena traducción en su lugar. El resultado ideal sería que el LLM ya no hiciera cambios a la traducción. Eso significa que el prompt general y tu prompt específico del idioma están tan bien como pueden estar (A veces hará algunos cambios aparentemente aleatorios; la razón es que <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">los LLMs no son algoritmos deterministas</a>).
Las pruebas:
@@ -23,7 +23,7 @@ Este es un fragmento de código: `foo`. Y este es otro fragmento de código: `ba
////
//// tab | Información
//// tab | Info
El contenido de los fragmentos de código debe dejarse tal cual.
@@ -45,7 +45,7 @@ El LLM probablemente traducirá esto mal. Lo interesante es si mantiene la tradu
////
//// tab | Información
//// tab | Info
La persona que diseña el prompt puede elegir si quiere convertir comillas neutras a comillas tipográficas. También está bien dejarlas como están.
@@ -67,7 +67,7 @@ Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you
////
//// tab | Información
//// tab | Info
... Sin embargo, las comillas dentro de fragmentos de código deben quedarse tal cual.
@@ -112,7 +112,7 @@ works(foo="bar") # Esto funciona 🎉
////
//// tab | Información
//// tab | Info
El código en bloques de código no debe modificarse, con la excepción de los comentarios.
@@ -154,7 +154,7 @@ Algo de texto
////
//// tab | Información
//// tab | Info
Las pestañas y los bloques `Info`/`Note`/`Warning`/etc. deben tener la traducción de su título añadida después de una barra vertical (`|`).
@@ -181,7 +181,7 @@ El texto del enlace debe traducirse, la dirección del enlace debe apuntar a la
////
//// tab | Información
//// tab | Info
Los enlaces deben traducirse, pero su dirección debe permanecer sin cambios. Una excepción son los enlaces absolutos a páginas de la documentación de FastAPI. En ese caso deben enlazar a la traducción.
@@ -197,10 +197,10 @@ Aquí algunas cosas envueltas en elementos HTML "abbr" (algunas son inventadas):
### El abbr da una frase completa { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done Hacer las cosas">GTD</abbr>
* <abbr title="less than menor que"><code>lt</code></abbr>
* <abbr title="XML Web Token Token web XML">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface Interfaz de pasarela de servidor paralela">PSGI</abbr>
* <abbr title="Getting Things Done - Hacer las cosas">GTD</abbr>
* <abbr title="less than - menor que"><code>lt</code></abbr>
* <abbr title="XML Web Token - Token web XML">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - Interfaz de pasarela de servidor paralela">PSGI</abbr>
### El abbr da una explicación { #the-abbr-gives-an-explanation }
@@ -209,12 +209,12 @@ Aquí algunas cosas envueltas en elementos HTML "abbr" (algunas son inventadas):
### El abbr da una frase completa y una explicación { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network Red de Desarrolladores de Mozilla: documentación para desarrolladores, escrita por la gente de Firefox">MDN</abbr>
* <abbr title="Input/Output Entrada/Salida: lectura o escritura de disco, comunicaciones de red.">I/O</abbr>.
* <abbr title="Mozilla Developer Network - Red de Desarrolladores de Mozilla: documentación para desarrolladores, escrita por la gente de Firefox">MDN</abbr>
* <abbr title="Input/Output: lectura o escritura de disco, comunicaciones de red.">I/O</abbr>.
////
//// tab | Información
//// tab | Info
Los atributos "title" de los elementos "abbr" se traducen siguiendo instrucciones específicas.
@@ -242,7 +242,7 @@ Hola de nuevo.
////
//// tab | Información
//// 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.
@@ -355,7 +355,7 @@ Para instrucciones específicas del idioma, mira p. ej. la sección `### Heading
* los headers
* el header de autorización
* el header `Authorization`
* el header Forwarded
* el header forwarded
* el sistema de inyección de dependencias
* la dependencia
@@ -368,7 +368,7 @@ Para instrucciones específicas del idioma, mira p. ej. la sección `### Heading
* paralelismo
* multiprocesamiento
* la variable de entorno
* la env var
* la variable de entorno
* el `PATH`
* la variable `PATH`
@@ -433,7 +433,7 @@ Para instrucciones específicas del idioma, mira p. ej. la sección `### Heading
* el motor de plantillas
* la anotación de tipos
* la anotación de tipos
* las anotaciones de tipos
* el worker del servidor
* el worker de Uvicorn
@@ -468,7 +468,7 @@ Para instrucciones específicas del idioma, mira p. ej. la sección `### Heading
* el ítem
* el paquete
* el lifespan
* el bloqueo
* el lock
* el middleware
* la aplicación móvil
* el módulo
@@ -494,7 +494,7 @@ Para instrucciones específicas del idioma, mira p. ej. la sección `### Heading
////
//// tab | Información
//// 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.

View File

@@ -10,7 +10,7 @@ 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_py39.py hl[6] *}
@@ -46,7 +46,7 @@ Para excluir una *path operation* del esquema OpenAPI generado (y por lo tanto,
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.
@@ -141,9 +141,9 @@ Podrías hacer eso con `openapi_extra`:
{* ../../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 <abbr title="convertido de algún formato plano, como bytes, a objetos de Python">parse</abbr> 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 <abbr title="converted from some plain format, like bytes, into Python objects - convertido de algún formato plano, como bytes, a objetos de Python">parse</abbr> 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 { #custom-openapi-content-type }
@@ -153,48 +153,16 @@ 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 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] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
////
/// info | Información
En la versión 1 de Pydantic el método para obtener el JSON Schema 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 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_py39.py hl[24:31] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
////
/// 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()`.
///
/// tip | Consejo
Aquí reutilizamos el mismo modelo Pydantic.

View File

@@ -46,12 +46,6 @@ $ pip install "fastapi[all]"
</div>
/// info | Información
En Pydantic v1 venía incluido con el paquete principal. Ahora se distribuye como este paquete independiente para que puedas elegir si instalarlo o no si no necesitas esa funcionalidad.
///
### Crear el objeto `Settings` { #create-the-settings-object }
Importa `BaseSettings` de Pydantic y crea una sub-clase, muy similar a un modelo de Pydantic.
@@ -60,31 +54,15 @@ De la misma forma que con los modelos de Pydantic, declaras atributos de clase c
Puedes usar todas las mismas funcionalidades de validación y herramientas que usas para los modelos de Pydantic, como diferentes tipos de datos y validaciones adicionales con `Field()`.
//// tab | Pydantic v2
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
////
//// tab | Pydantic v1
/// info | Información
En Pydantic v1 importarías `BaseSettings` directamente desde `pydantic` en lugar de desde `pydantic_settings`.
///
{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *}
////
/// tip | Consejo
Si quieres algo rápido para copiar y pegar, no uses este ejemplo, usa el último más abajo.
///
Luego, cuando creas una instance de esa clase `Settings` (en este caso, en el objeto `settings`), Pydantic leerá las variables de entorno de una manera indiferente a mayúsculas y minúsculas, por lo que una variable en mayúsculas `APP_NAME` aún será leída para el atributo `app_name`.
Luego, cuando creas un instance de esa clase `Settings` (en este caso, en el objeto `settings`), Pydantic leerá las variables de entorno de una manera indiferente a mayúsculas y minúsculas, por lo que una variable en mayúsculas `APP_NAME` aún será leída para el atributo `app_name`.
Luego convertirá y validará los datos. Así que, cuando uses ese objeto `settings`, tendrás datos de los tipos que declaraste (por ejemplo, `items_per_user` será un `int`).
@@ -110,7 +88,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p
/// tip | Consejo
Para establecer múltiples variables de entorno para un solo comando, simplemente sepáralas con un espacio y ponlas todas antes del comando.
Para establecer múltiples env vars para un solo comando, simplemente sepáralas con un espacio y ponlas todas antes del comando.
///
@@ -150,7 +128,7 @@ Proveniente del ejemplo anterior, tu archivo `config.py` podría verse como:
{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *}
Nota que ahora no creamos una instance por defecto `settings = Settings()`.
Nota que ahora no creamos un instance por defecto `settings = Settings()`.
### El archivo principal de la app { #the-main-app-file }
@@ -172,11 +150,11 @@ Y luego podemos requerirlo desde la *path operation function* como una dependenc
### Configuraciones y pruebas { #settings-and-testing }
Luego sería muy fácil proporcionar un objeto de configuraciones diferente durante las pruebas al sobrescribir una dependencia para `get_settings`:
Luego sería muy fácil proporcionar un objeto de configuraciones diferente durante las pruebas al crear una sobrescritura de dependencia para `get_settings`:
{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *}
En la dependencia sobreescrita establecemos un nuevo valor para el `admin_email` al crear el nuevo objeto `Settings`, y luego devolvemos ese nuevo objeto.
En la sobrescritura de dependencia establecemos un nuevo valor para el `admin_email` al crear el nuevo objeto `Settings`, y luego devolvemos ese nuevo objeto.
Luego podemos probar que se está usando.
@@ -215,8 +193,6 @@ APP_NAME="ChimichangApp"
Y luego actualizar tu `config.py` con:
//// tab | Pydantic v2
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
/// tip | Consejo
@@ -225,26 +201,6 @@ El atributo `model_config` se usa solo para configuración de Pydantic. Puedes l
///
////
//// tab | Pydantic v1
{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *}
/// tip | Consejo
La clase `Config` se usa solo para configuración de Pydantic. Puedes leer más en <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
///
////
/// info | Información
En la versión 1 de Pydantic la configuración se hacía en una clase interna `Config`, en la versión 2 de Pydantic se hace en un atributo `model_config`. Este atributo toma un `dict`, y para obtener autocompletado y errores en línea, puedes importar y usar `SettingsConfigDict` para definir ese `dict`.
///
Aquí definimos la configuración `env_file` dentro de tu clase Pydantic `Settings`, y establecemos el valor en el nombre del archivo con el archivo dotenv que queremos usar.
### Creando el `Settings` solo una vez con `lru_cache` { #creating-the-settings-only-once-with-lru-cache }
@@ -331,7 +287,7 @@ participant execute as Ejecutar función
end
```
En el caso de nuestra dependencia `get_settings()`, la función ni siquiera toma argumentos, por lo que siempre devolve el mismo valor.
En el caso de nuestra dependencia `get_settings()`, la función ni siquiera toma argumentos, por lo que siempre devuelve el mismo valor.
De esa manera, se comporta casi como si fuera solo una variable global. Pero como usa una función de dependencia, entonces podemos sobrescribirla fácilmente para las pruebas.

View File

@@ -2,21 +2,23 @@
Si tienes una app de FastAPI antigua, podrías estar usando Pydantic versión 1.
FastAPI ha tenido compatibilidad con Pydantic v1 o v2 desde la versión 0.100.0.
FastAPI versión 0.100.0 tenía compatibilidad con Pydantic v1 o v2. Usaba la que tuvieras instalada.
Si tenías instalado Pydantic v2, lo usaba. Si en cambio tenías Pydantic v1, usaba ese.
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.
Pydantic v1 está deprecado y su soporte se eliminará en las próximas versiones de FastAPI, deberías migrar a Pydantic v2. Así obtendrás las funcionalidades, mejoras y correcciones más recientes.
FastAPI 0.126.0 eliminó la compatibilidad con Pydantic v1, aunque siguió soportando `pydantic.v1` por un poquito más de tiempo.
/// warning | Advertencia
Además, el equipo de Pydantic dejó de dar soporte a Pydantic v1 para las versiones más recientes de Python, comenzando con Python 3.14.
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 nuevas funcionalidades en FastAPI 0.119.0 para ayudarte con una migración gradual.
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 }
@@ -44,9 +46,9 @@ Después de esto, puedes ejecutar los tests y revisa si todo funciona. Si es as
## Pydantic v1 en v2 { #pydantic-v1-in-v2 }
Pydantic v2 incluye todo lo de Pydantic v1 como un submódulo `pydantic.v1`.
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 ese submódulo, como si tuvieras instalado el Pydantic v1 antiguo.
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] *}
@@ -66,7 +68,7 @@ Ten en cuenta que, como el equipo de Pydantic ya no da soporte a Pydantic v1 en
### 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.
**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
@@ -106,7 +108,7 @@ graph TB
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:
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] *}
@@ -122,12 +124,12 @@ Si necesitas usar algunas de las herramientas específicas de FastAPI para pará
/// tip | Consejo
Primero prueba con `bump-pydantic`; si tus tests pasan y eso funciona, entonces terminaste con un solo comando. ✨
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, 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.
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.
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. 🚶

View File

@@ -1,6 +1,6 @@
# 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**.
@@ -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`. 🤓
@@ -100,5 +100,3 @@ Y ahora habrá un único esquema para entrada y salida para el modelo, solo `Ite
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
</div>
Este es el mismo comportamiento que en Pydantic v1. 🤓

View File

@@ -93,13 +93,13 @@ Las funcionalidades clave son:
"_Estoy súper emocionado con **FastAPI**. ¡Es tan divertido!_"
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">host del podcast Python Bytes</a></strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> host del podcast</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
---
"_Honestamente, lo que has construido parece súper sólido y pulido. En muchos aspectos, es lo que quería que **Hug** fuera; es realmente inspirador ver a alguien construir eso._"
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">creador de Hug</a></strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creador</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
@@ -117,6 +117,12 @@ Las funcionalidades clave son:
---
## Mini documental de FastAPI { #fastapi-mini-documentary }
Hay un <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">mini documental de FastAPI</a> lanzado a finales de 2025, puedes verlo online:
<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a>
## **Typer**, el FastAPI de las CLIs { #typer-the-fastapi-of-clis }
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
@@ -268,7 +274,7 @@ Verás la documentación interactiva automática de la API (proporcionada por <a
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Documentación de API Alternativa { #alternative-api-docs }
### Documentación alternativa de la API { #alternative-api-docs }
Y ahora, ve a <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
@@ -276,7 +282,7 @@ Verás la documentación alternativa automática (proporcionada por <a href="htt
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Actualización del Ejemplo { #example-upgrade }
## Actualización del ejemplo { #example-upgrade }
Ahora modifica el archivo `main.py` para recibir un body desde un request `PUT`.
@@ -314,7 +320,7 @@ def update_item(item_id: int, item: Item):
El servidor `fastapi dev` debería recargarse automáticamente.
### Actualización de la Documentación Interactiva de la API { #interactive-api-docs-upgrade }
### Actualización de la documentación interactiva de la API { #interactive-api-docs-upgrade }
Ahora ve a <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
@@ -330,7 +336,7 @@ Ahora ve a <a href="http://127.0.0.1:8000/docs" class="external-link" target="_b
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png)
### Actualización de la Documentación Alternativa de la API { #alternative-api-docs-upgrade }
### Actualización de la documentación alternativa de la API { #alternative-api-docs-upgrade }
Y ahora, ve a <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
@@ -393,13 +399,13 @@ Volviendo al ejemplo de código anterior, **FastAPI**:
* Validará que haya un `item_id` en el path para requests `GET` y `PUT`.
* Validará que el `item_id` sea del tipo `int` para requests `GET` y `PUT`.
* Si no lo es, el cliente verá un error útil y claro.
* Comprobará si hay un parámetro de query opcional llamado `q` (como en `http://127.0.0.1:8000/items/foo?q=somequery`) para requests `GET`.
* Revisa si hay un parámetro de query opcional llamado `q` (como en `http://127.0.0.1:8000/items/foo?q=somequery`) para requests `GET`.
* Como el parámetro `q` está declarado con `= None`, es opcional.
* Sin el `None` sería requerido (como lo es el body en el caso con `PUT`).
* Para requests `PUT` a `/items/{item_id}`, leerá el body como JSON:
* Comprobará que tiene un atributo requerido `name` que debe ser un `str`.
* Comprobará que tiene un atributo requerido `price` que debe ser un `float`.
* Comprobará que tiene un atributo opcional `is_offer`, que debe ser un `bool`, si está presente.
* Revisa que tiene un atributo requerido `name` que debe ser un `str`.
* Revisa que tiene un atributo requerido `price` que debe ser un `float`.
* Revisa que tiene un atributo opcional `is_offer`, que debe ser un `bool`, si está presente.
* Todo esto también funcionaría para objetos JSON profundamente anidados.
* Convertirá de y a JSON automáticamente.
* Documentará todo con OpenAPI, que puede ser usado por:

View File

@@ -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"
@@ -185,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
@@ -214,7 +214,7 @@ Así que usamos un import relativo con `..` para las dependencias:
/// 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.
///
@@ -271,7 +271,7 @@ 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 { #add-some-custom-tags-responses-and-dependencies }
@@ -283,7 +283,7 @@ Pero aún podemos agregar _más_ `tags` que se aplicarán a una *path operation*
/// 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`.
@@ -301,7 +301,7 @@ Y como la mayor parte de tu lógica ahora vivirá en su propio módulo específi
### 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`:
@@ -398,7 +398,7 @@ Incluirá todas las rutas de ese router como parte de ella.
En realidad creará internamente una *path operation* para cada *path operation* que fue declarada en el `APIRouter`.
Así, detrás de escena, funcionará como si todo fuera la misma única aplicación.
Así, detrás de escena, funcionará como si todo fuera la misma única app.
///
@@ -430,20 +430,20 @@ Podemos declarar todo eso sin tener que modificar el `APIRouter` original pasand
De esa manera, el `APIRouter` original permanecerá sin modificar, por lo que aún podemos compartir ese mismo archivo `app/internal/admin.py` con otros proyectos en la organización.
El resultado es que, en nuestra aplicación, cada una de las *path operations* del módulo `admin` tendrá:
El resultado es que, en nuestra app, cada una de las *path operations* del módulo `admin` tendrá:
* El prefix `/admin`.
* El tag `admin`.
* La dependencia `get_token_header`.
* La response `418`. 🍵
Pero eso solo afectará a ese `APIRouter` en nuestra aplicación, no en ningún otro código que lo utilice.
Pero eso solo afectará a ese `APIRouter` en nuestra app, no en ningún otro código que lo utilice.
Así, por ejemplo, otros proyectos podrían usar el mismo `APIRouter` con un método de autenticación diferente.
### Incluir una *path operation* { #include-a-path-operation }
También podemos agregar *path operations* directamente a la aplicación de `FastAPI`.
También podemos agregar *path operations* directamente a la app de `FastAPI`.
Aquí lo hacemos... solo para mostrar que podemos 🤷:
@@ -461,13 +461,13 @@ Los `APIRouter`s no están "montados", no están aislados del resto de la aplica
Esto se debe a que queremos incluir sus *path operations* en el esquema de OpenAPI y las interfaces de usuario.
Como no podemos simplemente aislarlos y "montarlos" independientemente del resto, se "clonan" las *path operations* (se vuelven a crear), no se incluyen directamente.
Como no podemos simplemente aislarlos y "montarlos" independientemente del resto, las *path operations* se "clonan" (se vuelven a crear), no se incluyen directamente.
///
## Revisa la documentación automática de la API { #check-the-automatic-api-docs }
Ahora, ejecuta tu aplicación:
Ahora, ejecuta tu app:
<div class="termy">
@@ -481,7 +481,7 @@ $ fastapi dev app/main.py
Y abre la documentación en <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
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:
<img src="/img/tutorial/bigger-applications/image01.png">
@@ -501,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.

View File

@@ -1,4 +1,4 @@
# Cuerpo - Actualizaciones { #body-updates }
# Body - Actualizaciones { #body-updates }
## Actualización reemplazando con `PUT` { #update-replacing-with-put }
@@ -50,14 +50,6 @@ Si quieres recibir actualizaciones parciales, es muy útil usar el parámetro `e
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:
@@ -68,14 +60,6 @@ Luego puedes usar esto para generar un `dict` solo con los datos que se establec
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] *}
@@ -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] *}

View File

@@ -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:
@@ -127,14 +128,6 @@ Dentro de la función, puedes acceder a todos los atributos del objeto modelo di
{* ../../docs_src/body/tutorial002_py310.py *}
/// info | Información
En Pydantic v1 el método se llamaba `.dict()`, se marcó como obsoleto (pero sigue soportado) en Pydantic v2, y se renombró a `.model_dump()`.
Los ejemplos aquí usan `.dict()` por compatibilidad con Pydantic v1, pero deberías usar `.model_dump()` si puedes usar Pydantic v2.
///
## Request body + parámetros de path { #request-body-path-parameters }
Puedes declarar parámetros de path y request body al mismo tiempo.
@@ -143,6 +136,7 @@ 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-query-parameters }
También puedes declarar parámetros de **body**, **path** y **query**, todos al mismo tiempo.
@@ -155,7 +149,7 @@ 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

View File

@@ -22,21 +22,13 @@ Aquí tienes una idea general de cómo podrían ser los modelos con sus campos d
{* ../../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()` { #about-user-in-dict }
#### `.dict()` de Pydantic { #pydantics-dict }
#### `.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
{
@@ -103,20 +95,20 @@ UserInDB(
#### 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.
@@ -125,7 +117,7 @@ Así, obtenemos un modelo Pydantic a partir de los datos en otro modelo Pydantic
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:
@@ -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 (anotaciones de tipos, 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.
@@ -180,20 +172,19 @@ Al definir una <a href="https://docs.pydantic.dev/latest/concepts/types/#unions"
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
### `Union` en Python 3.10 { #union-in-python-3-10 }
En este ejemplo pasamos `Union[PlaneItem, CarItem]` como el valor del argumento `response_model`.
Porque lo estamos pasando como un **valor a un argumento** en lugar de ponerlo en **anotaciones de tipos**, tenemos que usar `Union` incluso en Python 3.10.
Porque lo estamos pasando como un **valor a un argumento** en lugar de ponerlo en una **anotación de tipos**, tenemos que usar `Union` incluso en Python 3.10.
Si estuviera en anotaciones de tipos podríamos haber usado la barra vertical, como:
Si estuviera en una anotación de tipos podríamos haber usado la barra vertical, como:
```Python
some_variable: PlaneItem | CarItem
```
Pero si ponemos eso en la asignación `response_model=PlaneItem | CarItem` obtendríamos un error, porque Python intentaría realizar una **operación inválida** entre `PlaneItem` y `CarItem` en lugar de interpretar eso como anotaciones de tipos.
Pero si ponemos eso en la asignación `response_model=PlaneItem | CarItem` obtendríamos un error, porque Python intentaría realizar una **operación inválida** entre `PlaneItem` y `CarItem` en lugar de interpretar eso como una anotación de tipos.
## Lista de modelos { #list-of-models }
@@ -203,7 +194,6 @@ Para eso, usa el `typing.List` estándar de Python (o simplemente `list` en Pyth
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
## Response con `dict` arbitrario { #response-with-arbitrary-dict }
También puedes declarar un response usando un `dict` arbitrario plano, declarando solo el tipo de las claves y valores, sin usar un modelo Pydantic.
@@ -214,7 +204,6 @@ En este caso, puedes usar `typing.Dict` (o solo `dict` en Python 3.9 y posterior
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
## Recapitulación { #recap }
Usa múltiples modelos Pydantic y hereda libremente para cada caso.

View File

@@ -109,7 +109,7 @@ FastAPI ahora:
## Alternativa (antigua): `Query` como valor por defecto { #alternative-old-query-as-the-default-value }
Versiones anteriores de FastAPI (antes de <abbr title="antes de 2023-03">0.95.0</abbr>) 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 <abbr title="before 2023-03 - antes de 2023-03">0.95.0</abbr>) 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
@@ -192,7 +192,7 @@ También puedes agregar un parámetro `min_length`:
## Agregar expresiones regulares { #add-regular-expressions }
Puedes definir un <abbr title="Una expresión regular, regex o regexp es una secuencia de caracteres que define un patrón de búsqueda para strings.">expresión regular</abbr> `pattern` que el parámetro debe coincidir:
Puedes definir una <abbr title="Una expresión regular, regex o regexp es una secuencia de caracteres que define un patrón de búsqueda para strings.">expresión regular</abbr> `pattern` que el parámetro debe coincidir:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
@@ -206,20 +206,6 @@ Si te sientes perdido con todas estas ideas de **"expresión regular"**, no te p
Ahora sabes que cuando las necesites puedes usarlas en **FastAPI**.
### Pydantic v1 `regex` en lugar de `pattern` { #pydantic-v1-regex-instead-of-pattern }
Antes de la versión 2 de Pydantic y antes de FastAPI 0.100.0, el parámetro se llamaba `regex` en lugar de `pattern`, pero ahora está en desuso.
Todavía podrías ver algo de código que lo usa:
//// tab | Pydantic v1
{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
////
Pero que sepas que esto está deprecado y debería actualizarse para usar el nuevo parámetro `pattern`. 🤓
## Valores por defecto { #default-values }
Puedes, por supuesto, usar valores por defecto diferentes de `None`.
@@ -280,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:
@@ -386,7 +372,7 @@ Entonces puedes declarar un `alias`, y ese alias será usado para encontrar el v
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 <abbr title="obsoleto, se recomienda no usarlo">deprecated</abbr>.
Tienes que dejarlo allí por un tiempo porque hay clientes usándolo, pero quieres que la documentación lo muestre claramente como <abbr title="obsolete, recommended not to use it - obsoleto, se recomienda no usarlo">deprecated</abbr>.
Luego pasa el parámetro `deprecated=True` a `Query`:
@@ -416,7 +402,7 @@ Pydantic también tiene <a href="https://docs.pydantic.dev/latest/concepts/valid
///
Por ejemplo, este validador personalizado comprueba que el ID del ítem empiece con `isbn-` para un número de libro <abbr title="International Standard Book Number Número Estándar Internacional de Libro">ISBN</abbr> o con `imdb-` para un ID de URL de película de <abbr title="IMDB (Internet Movie Database) es un sitio web con información sobre películas">IMDB</abbr>:
Por ejemplo, este validador personalizado comprueba que el ID del ítem empiece con `isbn-` para un número de libro <abbr title="ISBN means International Standard Book Number - ISBN significa International Standard Book Number">ISBN</abbr> o con `imdb-` para un ID de URL de película de <abbr title="IMDB (Internet Movie Database) is a website with information about movies - IMDB (Internet Movie Database) es un sitio web con información sobre películas">IMDB</abbr>:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
@@ -450,7 +436,7 @@ Pero si te da curiosidad este ejemplo de código específico y sigues entretenid
#### Un ítem aleatorio { #a-random-item }
Con `data.items()` obtenemos un <abbr title="Algo que podemos iterar con un for, como una list, set, etc.">objeto iterable</abbr> con tuplas que contienen la clave y el valor para cada elemento del diccionario.
Con `data.items()` obtenemos un <abbr title="Algo que podemos iterar con un for loop, como una list, set, etc.">objeto iterable</abbr> 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())`.

View File

@@ -2,7 +2,7 @@
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, list, 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] *}
@@ -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*:
@@ -153,7 +153,7 @@ 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 un instance 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`.
@@ -252,20 +252,6 @@ Entonces, si envías un request a esa *path operation* para el ítem con ID `foo
/// 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()` en su lugar si puedes usar Pydantic v2.
///
/// info | Información
FastAPI usa el método `.dict()` del modelo de Pydantic con <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">su parámetro `exclude_unset`</a> para lograr esto.
///
/// info | Información
También puedes usar:
* `response_model_exclude_defaults=True`

View File

@@ -8,35 +8,13 @@ Aquí tienes varias formas de hacerlo.
Puedes declarar `examples` para un modelo de Pydantic que se añadirá al JSON Schema generado.
//// tab | Pydantic v2
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
////
Esa información extra se añadirá tal cual al **JSON Schema** resultante para ese modelo, y se usará en la documentación de la API.
//// tab | Pydantic v1
Puedes usar el atributo `model_config` que toma un `dict` como se describe en <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">la documentación de Pydantic: Configuración</a>.
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
////
Esa información extra se añadirá tal cual al **JSON Schema** generado para ese modelo, y se usará en la documentación de la API.
//// tab | Pydantic v2
En Pydantic versión 2, usarías el atributo `model_config`, que toma un `dict` como se describe en <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">la documentación de Pydantic: Configuración</a>.
Puedes establecer `"json_schema_extra"` con un `dict` que contenga cualquier dato adicional que desees que aparezca en el JSON Schema generado, incluyendo `examples`.
////
//// tab | Pydantic v1
En Pydantic versión 1, usarías una clase interna `Config` y `schema_extra`, como se describe en <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">la documentación de Pydantic: Personalización de Esquema</a>.
Puedes establecer `schema_extra` con un `dict` que contenga cualquier dato adicional que desees que aparezca en el JSON Schema generado, incluyendo `examples`.
////
Puedes establecer `"json_schema_extra"` con un `dict` que contenga cualquier dato adicional que te gustaría que aparezca en el JSON Schema generado, incluyendo `examples`.
/// tip | Consejo
@@ -50,7 +28,7 @@ Por ejemplo, podrías usarlo para añadir metadatos para una interfaz de usuario
OpenAPI 3.1.0 (usado desde FastAPI 0.99.0) añadió soporte para `examples`, que es parte del estándar de **JSON Schema**.
Antes de eso, solo soportaba la palabra clave `example` con un solo ejemplo. Eso aún es soportado por OpenAPI 3.1.0, pero está obsoleto y no es parte del estándar de JSON Schema. Así que se recomienda migrar de `example` a `examples`. 🤓
Antes de eso, solo soportaba la palabra clave `example` con un solo ejemplo. Eso aún es soportado por OpenAPI 3.1.0, pero está obsoleto y no es parte del estándar de JSON Schema. Así que se te anima a migrar `example` a `examples`. 🤓
Puedes leer más al final de esta página.
@@ -94,7 +72,7 @@ Por supuesto, también puedes pasar múltiples `examples`:
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
Cuando haces esto, los ejemplos serán parte del **JSON Schema** interno para esos datos de body.
Cuando haces esto, los ejemplos serán parte del **JSON Schema** interno para esos datos del body.
Sin embargo, al <abbr title="2023-08-26">momento de escribir esto</abbr>, Swagger UI, la herramienta encargada de mostrar la interfaz de documentación, no soporta mostrar múltiples ejemplos para los datos en **JSON Schema**. Pero lee más abajo para una solución alternativa.
@@ -203,17 +181,17 @@ Debido a eso, las versiones de FastAPI anteriores a 0.99.0 todavía usaban versi
### `examples` de Pydantic y FastAPI { #pydantic-and-fastapi-examples }
Cuando añades `examples` dentro de un modelo de Pydantic, usando `schema_extra` o `Field(examples=["algo"])`, ese ejemplo se añade al **JSON Schema** para ese modelo de Pydantic.
Cuando añades `examples` dentro de un modelo de Pydantic, usando `schema_extra` o `Field(examples=["something"])`, ese ejemplo se añade al **JSON Schema** para ese modelo de Pydantic.
Y ese **JSON Schema** del modelo de Pydantic se incluye en el **OpenAPI** de tu API, y luego se usa en la interfaz de documentación.
En las versiones de FastAPI antes de 0.99.0 (0.99.0 y superior usan el nuevo OpenAPI 3.1.0) cuando usabas `example` o `examples` con cualquiera de las otras utilidades (`Query()`, `Body()`, etc.) esos ejemplos no se añadían al JSON Schema que describe esos datos (ni siquiera a la propia versión de JSON Schema de OpenAPI), se añadían directamente a la declaración de la *path operation* en OpenAPI (fuera de las partes de OpenAPI que usan JSON Schema).
En las versiones de FastAPI antes de 0.99.0 (0.99.0 y superiores usan el nuevo OpenAPI 3.1.0) cuando usabas `example` o `examples` con cualquiera de las otras utilidades (`Query()`, `Body()`, etc.) esos ejemplos no se añadían al JSON Schema que describe esos datos (ni siquiera a la propia versión de JSON Schema de OpenAPI), se añadían directamente a la declaración de la *path operation* en OpenAPI (fuera de las partes de OpenAPI que usan JSON Schema).
Pero ahora que FastAPI 0.99.0 y superiores usa OpenAPI 3.1.0, que usa JSON Schema 2020-12, y Swagger UI 5.0.0 y superiores, todo es más consistente y los ejemplos se incluyen en JSON Schema.
### Swagger UI y `examples` específicos de OpenAPI { #swagger-ui-and-openapi-specific-examples }
Ahora, como Swagger UI no soportaba múltiples ejemplos de JSON Schema (a fecha de 2023-08-26), los usuarios no tenían una forma de mostrar múltiples ejemplos en los documentos.
Ahora, como Swagger UI no soportaba múltiples ejemplos de JSON Schema (a fecha de 2023-08-26), los usuarios no tenían una forma de mostrar múltiples ejemplos en la documentación.
Para resolver eso, FastAPI `0.103.0` **añadió soporte** para declarar el mismo viejo campo **específico de OpenAPI** `examples` con el nuevo parámetro `openapi_examples`. 🤓

View File

@@ -1,6 +1,6 @@
# Réponses supplémentaires dans OpenAPI
# Réponses supplémentaires dans OpenAPI { #additional-responses-in-openapi }
/// warning | Attention
/// warning | Alertes
Ceci concerne un sujet plutôt avancé.
@@ -8,29 +8,29 @@ Si vous débutez avec **FastAPI**, vous n'en aurez peut-être pas besoin.
///
Vous pouvez déclarer des réponses supplémentaires, avec des codes HTTP, des types de médias, des descriptions, etc.
Vous pouvez déclarer des réponses supplémentaires, avec des codes de statut supplémentaires, des types de médias, des descriptions, etc.
Ces réponses supplémentaires seront incluses dans le schéma OpenAPI, elles apparaîtront donc également dans la documentation de l'API.
Mais pour ces réponses supplémentaires, vous devez vous assurer de renvoyer directement une `Response` comme `JSONResponse`, avec votre code HTTP et votre contenu.
Mais pour ces réponses supplémentaires, vous devez vous assurer de renvoyer directement une `Response` comme `JSONResponse`, avec votre code de statut et votre contenu.
## Réponse supplémentaire avec `model`
## Réponse supplémentaire avec `model` { #additional-response-with-model }
Vous pouvez ajouter à votre décorateur de *paramètre de chemin* un paramètre `responses`.
Vous pouvez passer à vos *décorateurs de chemin d'accès* un paramètre `responses`.
Il prend comme valeur un `dict` dont les clés sont des codes HTTP pour chaque réponse, comme `200`, et la valeur de ces clés sont d'autres `dict` avec des informations pour chacun d'eux.
Il reçoit un `dict` : les clés sont des codes de statut pour chaque réponse (comme `200`), et les valeurs sont d'autres `dict` avec les informations pour chacune d'elles.
Chacun de ces `dict` de réponse peut avoir une clé `model`, contenant un modèle Pydantic, tout comme `response_model`.
**FastAPI** prendra ce modèle, générera son schéma JSON et l'inclura au bon endroit dans OpenAPI.
**FastAPI** prendra ce modèle, générera son JSON Schema et l'inclura au bon endroit dans OpenAPI.
Par exemple, pour déclarer une autre réponse avec un code HTTP `404` et un modèle Pydantic `Message`, vous pouvez écrire :
Par exemple, pour déclarer une autre réponse avec un code de statut `404` et un modèle Pydantic `Message`, vous pouvez écrire :
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
/// note | Remarque
Gardez à l'esprit que vous devez renvoyer directement `JSONResponse`.
Gardez à l'esprit que vous devez renvoyer directement le `JSONResponse`.
///
@@ -38,18 +38,18 @@ Gardez à l'esprit que vous devez renvoyer directement `JSONResponse`.
La clé `model` ne fait pas partie d'OpenAPI.
**FastAPI** prendra le modèle Pydantic à partir de là, générera le `JSON Schema` et le placera au bon endroit.
**FastAPI** prendra le modèle Pydantic à partir de là, générera le JSON Schema et le placera au bon endroit.
Le bon endroit est :
* Dans la clé `content`, qui a pour valeur un autre objet JSON (`dict`) qui contient :
* Une clé avec le type de support, par ex. `application/json`, qui contient comme valeur un autre objet JSON, qui contient :
* Une clé `schema`, qui a pour valeur le schéma JSON du modèle, voici le bon endroit.
* **FastAPI** ajoute ici une référence aux schémas JSON globaux à un autre endroit de votre OpenAPI au lieu de l'inclure directement. De cette façon, d'autres applications et clients peuvent utiliser ces schémas JSON directement, fournir de meilleurs outils de génération de code, etc.
* Dans la clé `content`, qui a pour valeur un autre objet JSON (`dict`) qui contient :
* Une clé avec le type de média, par ex. `application/json`, qui contient comme valeur un autre objet JSON, qui contient :
* Une clé `schema`, qui a pour valeur le JSON Schema du modèle, voici le bon endroit.
* **FastAPI** ajoute ici une référence aux JSON Schemas globaux à un autre endroit de votre OpenAPI au lieu de l'inclure directement. De cette façon, d'autres applications et clients peuvent utiliser ces JSON Schemas directement, fournir de meilleurs outils de génération de code, etc.
///
Les réponses générées au format OpenAPI pour cette *opération de chemin* seront :
Les réponses générées dans OpenAPI pour cette *opération de chemin d'accès* seront :
```JSON hl_lines="3-12"
{
@@ -88,7 +88,7 @@ Les réponses générées au format OpenAPI pour cette *opération de chemin* se
}
```
Les schémas sont référencés à un autre endroit du modèle OpenAPI :
Les schémas sont référencés à un autre endroit dans le schéma OpenAPI :
```JSON hl_lines="4-16"
{
@@ -169,13 +169,13 @@ Les schémas sont référencés à un autre endroit du modèle OpenAPI :
}
```
## Types de médias supplémentaires pour la réponse principale
## Types de médias supplémentaires pour la réponse principale { #additional-media-types-for-the-main-response }
Vous pouvez utiliser ce même paramètre `responses` pour ajouter différents types de médias pour la même réponse principale.
Par exemple, vous pouvez ajouter un type de média supplémentaire `image/png`, en déclarant que votre *opération de chemin* peut renvoyer un objet JSON (avec le type de média `application/json`) ou une image PNG :
Par exemple, vous pouvez ajouter un type de média supplémentaire `image/png`, en déclarant que votre *opération de chemin d'accès* peut renvoyer un objet JSON (avec le type de média `application/json`) ou une image PNG :
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
/// note | Remarque
@@ -191,29 +191,29 @@ Mais si vous avez spécifié une classe de réponse personnalisée avec `None` c
///
## Combinaison d'informations
## Combinaison d'informations { #combining-information }
Vous pouvez également combiner des informations de réponse provenant de plusieurs endroits, y compris les paramètres `response_model`, `status_code` et `responses`.
Vous pouvez déclarer un `response_model`, en utilisant le code HTTP par défaut `200` (ou un code personnalisé si vous en avez besoin), puis déclarer des informations supplémentaires pour cette même réponse dans `responses`, directement dans le schéma OpenAPI.
Vous pouvez déclarer un `response_model`, en utilisant le code de statut par défaut `200` (ou un code personnalisé si vous en avez besoin), puis déclarer des informations supplémentaires pour cette même réponse dans `responses`, directement dans le schéma OpenAPI.
**FastAPI** conservera les informations supplémentaires des `responses` et les combinera avec le schéma JSON de votre modèle.
**FastAPI** conservera les informations supplémentaires des `responses` et les combinera avec le JSON Schema de votre modèle.
Par exemple, vous pouvez déclarer une réponse avec un code HTTP `404` qui utilise un modèle Pydantic et a une `description` personnalisée.
Par exemple, vous pouvez déclarer une réponse avec un code de statut `404` qui utilise un modèle Pydantic et a une `description` personnalisée.
Et une réponse avec un code HTTP `200` qui utilise votre `response_model`, mais inclut un `example` personnalisé :
Et une réponse avec un code de statut `200` qui utilise votre `response_model`, mais inclut un `example` personnalisé :
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
Tout sera combiné et inclus dans votre OpenAPI, et affiché dans la documentation de l'API :
Tout sera combiné et inclus dans votre OpenAPI, et affiché dans la documentation de l'API :
<img src="/img/tutorial/additional-responses/image01.png">
## Combinez les réponses prédéfinies et les réponses personnalisées
## Combiner des réponses prédéfinies et des réponses personnalisées { #combine-predefined-responses-and-custom-ones }
Vous voulez peut-être avoir des réponses prédéfinies qui s'appliquent à de nombreux *paramètre de chemin*, mais vous souhaitez les combiner avec des réponses personnalisées nécessaires à chaque *opération de chemin*.
Vous voulez peut-être avoir des réponses prédéfinies qui s'appliquent à de nombreuses *opérations de chemin d'accès*, mais vous souhaitez les combiner avec des réponses personnalisées nécessaires à chaque *opération de chemin d'accès*.
Dans ces cas, vous pouvez utiliser la technique Python "d'affection par décomposition" (appelé _unpacking_ en anglais) d'un `dict` avec `**dict_to_unpack` :
Dans ces cas, vous pouvez utiliser la technique Python de « unpacking » d'un `dict` avec `**dict_to_unpack` :
```Python
old_dict = {
@@ -223,7 +223,7 @@ old_dict = {
new_dict = {**old_dict, "new key": "new value"}
```
Ici, `new_dict` contiendra toutes les paires clé-valeur de `old_dict` plus la nouvelle paire clé-valeur :
Ici, `new_dict` contiendra toutes les paires clé-valeur de `old_dict` plus la nouvelle paire clé-valeur :
```Python
{
@@ -233,15 +233,15 @@ Ici, `new_dict` contiendra toutes les paires clé-valeur de `old_dict` plus la n
}
```
Vous pouvez utiliser cette technique pour réutiliser certaines réponses prédéfinies dans vos *paramètres de chemin* et les combiner avec des réponses personnalisées supplémentaires.
Vous pouvez utiliser cette technique pour réutiliser certaines réponses prédéfinies dans vos *opérations de chemin d'accès* et les combiner avec des réponses personnalisées supplémentaires.
Par exemple:
Par exemple :
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
## Plus d'informations sur les réponses OpenAPI
## Plus d'informations sur les réponses OpenAPI { #more-information-about-openapi-responses }
Pour voir exactement ce que vous pouvez inclure dans les réponses, vous pouvez consulter ces sections dans la spécification OpenAPI :
Pour voir exactement ce que vous pouvez inclure dans les réponses, vous pouvez consulter ces sections dans la spécification OpenAPI :
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject" class="external-link" target="_blank">Objet Responses de OpenAPI </a>, il inclut le `Response Object`.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject" class="external-link" target="_blank">Objet Response de OpenAPI </a>, vous pouvez inclure n'importe quoi directement dans chaque réponse à l'intérieur de votre paramètre `responses`. Y compris `description`, `headers`, `content` (à l'intérieur de cela, vous déclarez différents types de médias et schémas JSON) et `links`.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">Objet Responses d'OpenAPI</a>, il inclut le `Response Object`.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">Objet Response d'OpenAPI</a>, vous pouvez inclure n'importe quoi à partir de celui-ci directement dans chaque réponse à l'intérieur de votre paramètre `responses`. Y compris `description`, `headers`, `content` (à l'intérieur de cela, vous déclarez différents types de médias et JSON Schemas), et `links`.

View File

@@ -1,28 +1,28 @@
# Codes HTTP supplémentaires
# Codes de statut supplémentaires { #additional-status-codes }
Par défaut, **FastAPI** renverra les réponses à l'aide d'une structure de données `JSONResponse`, en plaçant la réponse de votre *chemin d'accès* à l'intérieur de cette `JSONResponse`.
Par défaut, **FastAPI** renverra les réponses en utilisant une `JSONResponse`, en plaçant le contenu que vous renvoyez depuis votre *chemin d'accès* à l'intérieur de cette `JSONResponse`.
Il utilisera le code HTTP par défaut ou celui que vous avez défini dans votre *chemin d'accès*.
Il utilisera le code de statut par défaut ou celui que vous définissez dans votre *chemin d'accès*.
## Codes HTTP supplémentaires
## Codes de statut supplémentaires { #additional-status-codes_1 }
Si vous souhaitez renvoyer des codes HTTP supplémentaires en plus du code principal, vous pouvez le faire en renvoyant directement une `Response`, comme une `JSONResponse`, et en définissant directement le code HTTP supplémentaire.
Si vous souhaitez renvoyer des codes de statut supplémentaires en plus du code principal, vous pouvez le faire en renvoyant directement une `Response`, comme une `JSONResponse`, et en définissant directement le code de statut supplémentaire.
Par exemple, disons que vous voulez avoir un *chemin d'accès* qui permet de mettre à jour les éléments et renvoie les codes HTTP 200 "OK" en cas de succès.
Par exemple, disons que vous voulez avoir un *chemin d'accès* qui permet de mettre à jour des éléments et renvoie des codes de statut HTTP 200 « OK » en cas de succès.
Mais vous voulez aussi qu'il accepte de nouveaux éléments. Et lorsque les éléments n'existaient pas auparavant, il les crée et renvoie un code HTTP de 201 "Créé".
Mais vous voulez aussi qu'il accepte de nouveaux éléments. Et lorsque les éléments n'existaient pas auparavant, il les crée et renvoie un code de statut HTTP de 201 « Created ».
Pour y parvenir, importez `JSONResponse` et renvoyez-y directement votre contenu, en définissant le `status_code` que vous souhaitez :
Pour y parvenir, importez `JSONResponse` et renvoyez-y directement votre contenu, en définissant le `status_code` que vous voulez :
{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *}
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
/// warning | Attention
/// warning | Alertes
Lorsque vous renvoyez une `Response` directement, comme dans l'exemple ci-dessus, elle sera renvoyée directement.
Elle ne sera pas sérialisée avec un modèle.
Elle ne sera pas sérialisée avec un modèle, etc.
Assurez-vous qu'il contient les données souhaitées et que les valeurs soient dans un format JSON valides (si vous utilisez une `JSONResponse`).
Vous devez vous assurer qu'elle contient les données que vous voulez qu'elle contienne, et que les valeurs sont du JSON valide (si vous utilisez `JSONResponse`).
///
@@ -30,12 +30,12 @@ Assurez-vous qu'il contient les données souhaitées et que les valeurs soient d
Vous pouvez également utiliser `from starlette.responses import JSONResponse`.
Pour plus de commodités, **FastAPI** fournit les objets `starlette.responses` sous forme d'un alias accessible par `fastapi.responses`. Mais la plupart des réponses disponibles proviennent directement de Starlette. Il en est de même avec l'objet `statut`.
**FastAPI** fournit les mêmes `starlette.responses` que `fastapi.responses` uniquement par commodité pour vous, le développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. De même pour `status`.
///
## Documents OpenAPI et API
## Documents OpenAPI et API { #openapi-and-api-docs }
Si vous renvoyez directement des codes HTTP et des réponses supplémentaires, ils ne seront pas inclus dans le schéma OpenAPI (la documentation de l'API), car FastAPI n'a aucun moyen de savoir à l'avance ce que vous allez renvoyer.
Si vous renvoyez directement des codes de statut et des réponses supplémentaires, ils ne seront pas inclus dans le schéma OpenAPI (les documents de l'API), car FastAPI n'a aucun moyen de savoir à l'avance ce que vous allez renvoyer.
Mais vous pouvez documenter cela dans votre code, en utilisant : [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}.
Mais vous pouvez documenter cela dans votre code, en utilisant : [Réponses supplémentaires](additional-responses.md){.internal-link target=_blank}.

View File

@@ -1,27 +1,21 @@
# Guide de l'utilisateur avancé
# Guide de l'utilisateur avancé { #advanced-user-guide }
## Caractéristiques supplémentaires
## Caractéristiques supplémentaires { #additional-features }
Le [Tutoriel - Guide de l'utilisateur](../tutorial/index.md){.internal-link target=_blank} devrait suffire à vous faire découvrir toutes les fonctionnalités principales de **FastAPI**.
Dans les sections suivantes, vous verrez des options, configurations et fonctionnalités supplémentaires.
/// note | Remarque
/// tip | Astuce
Les sections de ce chapitre ne sont **pas nécessairement "avancées"**.
Les sections suivantes ne sont **pas nécessairement « avancées »**.
Et il est possible que pour votre cas d'utilisation, la solution se trouve dans l'un d'entre eux.
Et il est possible que, pour votre cas d'utilisation, la solution se trouve dans l'une d'entre elles.
///
## Lisez d'abord le didacticiel
## Lisez d'abord le didacticiel { #read-the-tutorial-first }
Vous pouvez utiliser la plupart des fonctionnalités de **FastAPI** grâce aux connaissances du [Tutoriel - Guide de l'utilisateur](../tutorial/index.md){.internal-link target=_blank}.
Vous pouvez toujours utiliser la plupart des fonctionnalités de **FastAPI** avec les connaissances du [Tutoriel - Guide de l'utilisateur](../tutorial/index.md){.internal-link target=_blank}.
Et les sections suivantes supposent que vous l'avez lu et que vous en connaissez les idées principales.
## Cours TestDriven.io
Si vous souhaitez suivre un cours pour débutants avancés pour compléter cette section de la documentation, vous pouvez consulter : <a href="https://testdrive.io/courses/tdd-fastapi/" class="external- link" target="_blank">Développement piloté par les tests avec FastAPI et Docker</a> par **TestDriven.io**.
10 % de tous les bénéfices de ce cours sont reversés au développement de **FastAPI**. 🎉 😄
Et les sections suivantes supposent que vous l'avez déjà lu, et supposent que vous connaissez ces idées principales.

View File

@@ -1,26 +1,26 @@
# Configuration avancée des paramètres de chemin
# Configuration avancée des chemins d'accès { #path-operation-advanced-configuration }
## ID d'opération OpenAPI
## operationId OpenAPI { #openapi-operationid }
/// warning | Attention
/// warning | Alertes
Si vous n'êtes pas un "expert" en OpenAPI, vous n'en avez probablement pas besoin.
Si vous nêtes pas un « expert » en OpenAPI, vous nen avez probablement pas besoin.
///
Dans OpenAPI, les chemins sont des ressources, tels que /users/ ou /items/, exposées par votre API, et les opérations sont les méthodes HTTP utilisées pour manipuler ces chemins, telles que GET, POST ou DELETE. Les operationId sont des chaînes uniques facultatives utilisées pour identifier une opération d'un chemin. Vous pouvez définir l'OpenAPI `operationId` à utiliser dans votre *opération de chemin* avec le paramètre `operation_id`.
Vous pouvez définir lOpenAPI `operationId` à utiliser dans votre *chemin d'accès* avec le paramètre `operation_id`.
Vous devez vous assurer qu'il est unique pour chaque opération.
Vous devez vous assurer quil est unique pour chaque opération.
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
### Utilisation du nom *path operation function* comme operationId
### Utiliser le nom de la *fonction de chemin d'accès* comme operationId { #using-the-path-operation-function-name-as-the-operationid }
Si vous souhaitez utiliser les noms de fonction de vos API comme `operationId`, vous pouvez les parcourir tous et remplacer chaque `operation_id` de l'*opération de chemin* en utilisant leur `APIRoute.name`.
Si vous souhaitez utiliser les noms de fonction de vos API comme `operationId`, vous pouvez les parcourir tous et remplacer chaque `operation_id` des *chemins d'accès* en utilisant leur `APIRoute.name`.
Vous devriez le faire après avoir ajouté toutes vos *paramètres de chemin*.
Vous devez le faire après avoir ajouté tous vos *chemins d'accès*.
{* ../../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 | Astuce
@@ -28,79 +28,81 @@ Si vous appelez manuellement `app.openapi()`, vous devez mettre à jour les `ope
///
/// warning | Attention
/// warning | Alertes
Pour faire cela, vous devez vous assurer que chacun de vos *chemin* ait un nom unique.
Si vous faites cela, vous devez vous assurer que chacune de vos *fonctions de chemin d'accès* a un nom unique.
Même s'ils se trouvent dans des modules différents (fichiers Python).
Même si elles sont dans des modules différents (fichiers Python).
///
## Exclusion d'OpenAPI
## Exclusion d'OpenAPI { #exclude-from-openapi }
Pour exclure un *chemin* du schéma OpenAPI généré (et donc des systèmes de documentation automatiques), utilisez le paramètre `include_in_schema` et assignez-lui la valeur `False` :
Pour exclure un *chemin d'accès* du schéma OpenAPI généré (et donc des systèmes de documentation automatiques), utilisez le paramètre `include_in_schema` et définissez-le à `False` :
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
## Description avancée de docstring
## Description avancée depuis la docstring { #advanced-description-from-docstring }
Vous pouvez limiter le texte utilisé de la docstring d'une *fonction de chemin* qui sera affiché sur OpenAPI.
Vous pouvez limiter les lignes utilisées depuis la docstring dune *fonction de chemin d'accès* pour OpenAPI.
L'ajout d'un `\f` (un caractère d'échappement "form feed") va permettre à **FastAPI** de tronquer la sortie utilisée pour OpenAPI à ce stade.
Ajouter un `\f` (un caractère « form feed » échappé) amène **FastAPI** à tronquer la sortie utilisée pour OpenAPI à cet endroit.
Il n'apparaîtra pas dans la documentation, mais d'autres outils (tel que Sphinx) pourront utiliser le reste.
Il napparaîtra pas dans la documentation, mais dautres outils (tels que Sphinx) pourront utiliser le reste.
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
## Réponses supplémentaires
## Réponses supplémentaires { #additional-responses }
Vous avez probablement vu comment déclarer le `response_model` et le `status_code` pour une *opération de chemin*.
Vous avez probablement vu comment déclarer le `response_model` et le `status_code` pour un *chemin d'accès*.
Cela définit les métadonnées sur la réponse principale d'une *opération de chemin*.
Cela définit les métadonnées concernant la réponse principale dun *chemin d'accès*.
Vous pouvez également déclarer des réponses supplémentaires avec leurs modèles, codes de statut, etc.
Il y a un chapitre entier ici dans la documentation à ce sujet, vous pouvez le lire sur [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}.
Il y a un chapitre entier dans la documentation à ce sujet, vous pouvez le lire dans [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}.
## OpenAPI supplémentaire
## OpenAPI Extra { #openapi-extra }
Lorsque vous déclarez un *chemin* dans votre application, **FastAPI** génère automatiquement les métadonnées concernant ce *chemin* à inclure dans le schéma OpenAPI.
Lorsque vous déclarez un *chemin d'accès* dans votre application, **FastAPI** génère automatiquement les métadonnées pertinentes concernant ce *chemin d'accès* à inclure dans le schéma OpenAPI.
/// note | Détails techniques
La spécification OpenAPI appelle ces métadonnées des <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Objets d'opération</a>.
Dans la spécification OpenAPI, cela sappelle l<a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>.
///
Il contient toutes les informations sur le *chemin* et est utilisé pour générer automatiquement la documentation.
Il contient toutes les informations sur le *chemin d'accès* et est utilisé pour générer la documentation automatique.
Il inclut les `tags`, `parameters`, `requestBody`, `responses`, etc.
Ce schéma OpenAPI spécifique aux *operations* est normalement généré automatiquement par **FastAPI**, mais vous pouvez également l'étendre.
Ce schéma OpenAPI spécifique au *chemin d'accès* est normalement généré automatiquement par **FastAPI**, mais vous pouvez aussi létendre.
/// tip | Astuce
Si vous avez seulement besoin de déclarer des réponses supplémentaires, un moyen plus pratique de le faire est d'utiliser les [réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}.
Cest un point dextension de bas niveau.
Si vous avez uniquement besoin de déclarer des réponses supplémentaires, une manière plus pratique de le faire est avec [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}.
///
Vous pouvez étendre le schéma OpenAPI pour une *opération de chemin* en utilisant le paramètre `openapi_extra`.
Vous pouvez étendre le schéma OpenAPI pour un *chemin d'accès* en utilisant le paramètre `openapi_extra`.
### Extensions OpenAPI
### Extensions OpenAPI { #openapi-extensions }
Cet `openapi_extra` peut être utile, par exemple, pour déclarer [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) :
Cet `openapi_extra` peut être utile, par exemple, pour déclarer des [OpenAPI Extensions](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 vous ouvrez la documentation automatique de l'API, votre extension apparaîtra au bas du *chemin* spécifique.
Si vous ouvrez les documents automatiques de lAPI, votre extension apparaîtra au bas du *chemin d'accès* spécifique.
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
Et dans le fichier openapi généré (`/openapi.json`), vous verrez également votre extension dans le cadre du *chemin* spécifique :
Et si vous regardez lOpenAPI résultant (dans `/openapi.json` de votre API), vous verrez aussi votre extension comme faisant partie du *chemin d'accès* spécifique :
```JSON hl_lines="22"
{
"openapi": "3.0.2",
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
@@ -127,44 +129,44 @@ Et dans le fichier openapi généré (`/openapi.json`), vous verrez également v
}
```
### Personnalisation du Schéma OpenAPI pour un chemin
### Schéma OpenAPI personnalisé pour un *chemin d'accès* { #custom-openapi-path-operation-schema }
Le dictionnaire contenu dans la variable `openapi_extra` sera fusionné avec le schéma OpenAPI généré automatiquement pour l'*opération de chemin*.
Le dictionnaire dans `openapi_extra` sera fusionné en profondeur avec le schéma OpenAPI généré automatiquement pour le *chemin d'accès*.
Ainsi, vous pouvez ajouter des données supplémentaires au schéma généré automatiquement.
Ainsi, vous pourriez ajouter des données supplémentaires au schéma généré automatiquement.
Par exemple, vous pouvez décider de lire et de valider la requête avec votre propre code, sans utiliser les fonctionnalités automatiques de validation proposée par Pydantic, mais vous pouvez toujours définir la requête dans le schéma OpenAPI.
Par exemple, vous pourriez décider de lire et de valider la requête avec votre propre code, sans utiliser les fonctionnalités automatiques de FastAPI avec Pydantic, mais vous pourriez tout de même vouloir définir la requête dans le schéma OpenAPI.
Vous pouvez le faire avec `openapi_extra` :
Vous pourriez faire cela avec `openapi_extra` :
{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[20:37,39:40] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
Dans cet exemple, nous n'avons déclaré aucun modèle Pydantic. En fait, le corps de la requête n'est même pas <abbr title="converti d'un format simple, comme des octets, en objets Python">parsé</abbr> en tant que JSON, il est lu directement en tant que `bytes`, et la fonction `magic_data_reader()` serait chargé de l'analyser d'une manière ou d'une autre.
Dans cet exemple, nous navons déclaré aucun modèle Pydantic. En fait, le corps de la requête nest même pas <abbr title="converted from some plain format, like bytes, into Python objects - converti depuis un format simple, comme des octets, en objets Python">parsed</abbr> en tant que JSON, il est lu directement en tant que `bytes`, et la fonction `magic_data_reader()` serait chargée de lanalyser dune manière ou dune autre.
Néanmoins, nous pouvons déclarer le schéma attendu pour le corps de la requête.
### Type de contenu OpenAPI personnalisé
### Type de contenu OpenAPI personnalisé { #custom-openapi-content-type }
En utilisant cette même astuce, vous pouvez utiliser un modèle Pydantic pour définir le schéma JSON qui est ensuite inclus dans la section de schéma OpenAPI personnalisée pour le *chemin* concerné.
En utilisant cette même astuce, vous pourriez utiliser un modèle Pydantic pour définir le JSON Schema qui est ensuite inclus dans la section de schéma OpenAPI personnalisée pour le *chemin d'accès*.
Et vous pouvez le faire même si le type de données dans la requête n'est pas au format JSON.
Et vous pourriez faire cela même si le type de données dans la requête nest pas du JSON.
Dans cet exemple, nous n'utilisons pas les fonctionnalités de FastAPI pour extraire le schéma JSON des modèles Pydantic ni la validation automatique pour JSON. En fait, nous déclarons le type de contenu de la requête en tant que YAML, et non JSON :
Par exemple, dans cette application, nous nutilisons pas la fonctionnalité intégrée de FastAPI pour extraire le JSON Schema depuis les modèles Pydantic ni la validation automatique pour le JSON. En fait, nous déclarons le type de contenu de la requête comme étant du YAML, pas du JSON :
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22,24] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
Néanmoins, bien que nous n'utilisions pas la fonctionnalité par défaut, nous utilisons toujours un modèle Pydantic pour générer manuellement le schéma JSON pour les données que nous souhaitons recevoir en YAML.
Néanmoins, bien que nous nutilisions pas la fonctionnalité intégrée par défaut, nous utilisons tout de même un modèle Pydantic pour générer manuellement le JSON Schema pour les données que nous voulons recevoir en YAML.
Ensuite, nous utilisons directement la requête et extrayons son contenu en tant qu'octets. Cela signifie que FastAPI n'essaiera même pas d'analyser le payload de la requête en tant que JSON.
Ensuite, nous utilisons la requête directement et extrayons le corps en tant que `bytes`. Cela signifie que FastAPI nessaiera même pas danalyser le payload de la requête comme du JSON.
Et nous analysons directement ce contenu YAML, puis nous utilisons à nouveau le même modèle Pydantic pour valider le contenu YAML :
Et ensuite, dans notre code, nous analysons directement ce contenu YAML, et puis nous utilisons à nouveau le même modèle Pydantic pour valider le contenu YAML :
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
/// tip | Astuce
Ici, nous réutilisons le même modèle Pydantic.
Mais nous aurions pu tout aussi bien pu le valider d'une autre manière.
Mais, de la même manière, nous aurions pu le valider dune autre façon.
///

View File

@@ -1,20 +1,20 @@
# Renvoyer directement une réponse
# Renvoyer directement une réponse { #return-a-response-directly }
Lorsque vous créez une *opération de chemins* **FastAPI**, vous pouvez normalement retourner n'importe quelle donnée : un `dict`, une `list`, un modèle Pydantic, un modèle de base de données, etc.
Lorsque vous créez un *chemin d'accès* **FastAPI**, vous pouvez normalement en retourner n'importe quelle donnée : un `dict`, une `list`, un modèle Pydantic, un modèle de base de données, etc.
Par défaut, **FastAPI** convertirait automatiquement cette valeur de retour en JSON en utilisant le `jsonable_encoder` expliqué dans [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}.
Par défaut, **FastAPI** convertirait automatiquement cette valeur de retour en JSON en utilisant le `jsonable_encoder` expliqué dans [Encodeur compatible JSON](../tutorial/encoder.md){.internal-link target=_blank}.
Ensuite, en arrière-plan, il mettra ces données JSON-compatible (par exemple un `dict`) à l'intérieur d'un `JSONResponse` qui sera utilisé pour envoyer la réponse au client.
Ensuite, en arrière-plan, il mettrait ces données compatibles JSON (par exemple un `dict`) à l'intérieur d'une `JSONResponse` qui serait utilisée pour envoyer la réponse au client.
Mais vous pouvez retourner une `JSONResponse` directement à partir de vos *opérations de chemin*.
Mais vous pouvez retourner une `JSONResponse` directement à partir de vos *chemins d'accès*.
Cela peut être utile, par exemple, pour retourner des en-têtes personnalisés ou des cookies.
## Renvoyer une `Response`
## Renvoyer une `Response` { #return-a-response }
En fait, vous pouvez retourner n'importe quelle `Response` ou n'importe quelle sous-classe de celle-ci.
/// note | Remarque
/// tip | Astuce
`JSONResponse` est elle-même une sous-classe de `Response`.
@@ -22,29 +22,29 @@ En fait, vous pouvez retourner n'importe quelle `Response` ou n'importe quelle s
Et quand vous retournez une `Response`, **FastAPI** la transmet directement.
Elle ne fera aucune conversion de données avec les modèles Pydantic, elle ne convertira pas le contenu en un type quelconque.
Elle ne fera aucune conversion de données avec les modèles Pydantic, elle ne convertira pas le contenu en un type quelconque, etc.
Cela vous donne beaucoup de flexibilité. Vous pouvez retourner n'importe quel type de données, surcharger n'importe quelle déclaration ou validation de données.
Cela vous donne beaucoup de flexibilité. Vous pouvez retourner n'importe quel type de données, surcharger n'importe quelle déclaration ou validation de données, etc.
## Utiliser le `jsonable_encoder` dans une `Response`
## Utiliser le `jsonable_encoder` dans une `Response` { #using-the-jsonable-encoder-in-a-response }
Parce que **FastAPI** n'apporte aucune modification à une `Response` que vous retournez, vous devez vous assurer que son contenu est prêt à être utilisé (sérialisable).
Parce que **FastAPI** n'apporte aucune modification à une `Response` que vous retournez, vous devez vous assurer que son contenu est prêt.
Par exemple, vous ne pouvez pas mettre un modèle Pydantic dans une `JSONResponse` sans d'abord le convertir en un `dict` avec tous les types de données (comme `datetime`, `UUID`, etc.) convertis en types compatibles avec JSON.
Par exemple, vous ne pouvez pas mettre un modèle Pydantic dans une `JSONResponse` sans d'abord le convertir en un `dict` avec tous les types de données (comme `datetime`, `UUID`, etc) convertis en types compatibles avec JSON.
Pour ces cas, vous pouvez spécifier un appel à `jsonable_encoder` pour convertir vos données avant de les passer à une réponse :
Pour ces cas, vous pouvez utiliser le `jsonable_encoder` pour convertir vos données avant de les passer à une réponse :
{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
/// note | Détails techniques
Vous pouvez aussi utiliser `from starlette.responses import JSONResponse`.
**FastAPI** fournit le même objet `starlette.responses` que `fastapi.responses` juste par commodité pour le développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette.
**FastAPI** fournit le même objet `starlette.responses` que `fastapi.responses` juste par commodité pour vous, le développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette.
///
## Renvoyer une `Response` personnalisée
## Renvoyer une `Response` personnalisée { #returning-a-custom-response }
L'exemple ci-dessus montre toutes les parties dont vous avez besoin, mais il n'est pas encore très utile, car vous auriez pu retourner l'`item` directement, et **FastAPI** l'aurait mis dans une `JSONResponse` pour vous, en le convertissant en `dict`, etc. Tout cela par défaut.
@@ -54,12 +54,12 @@ Disons que vous voulez retourner une réponse <a href="https://en.wikipedia.org/
Vous pouvez mettre votre contenu XML dans une chaîne de caractères, la placer dans une `Response`, et la retourner :
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
## Notes
## Notes { #notes }
Lorsque vous renvoyez une `Response` directement, ses données ne sont pas validées, converties (sérialisées), ni documentées automatiquement.
Mais vous pouvez toujours les documenter comme décrit dans [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}.
Mais vous pouvez toujours les documenter comme décrit dans [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}.
Vous pouvez voir dans les sections suivantes comment utiliser/déclarer ces `Response`s personnalisées tout en conservant la conversion automatique des données, la documentation, etc.

View File

@@ -1,34 +1,34 @@
# Test de performance
# Benchmarks { #benchmarks }
Les tests de performance de TechEmpower montrent que les applications **FastAPI** tournant sous Uvicorn comme <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">étant l'un des frameworks Python les plus rapides disponibles</a>, seulement inférieur à Starlette et Uvicorn (tous deux utilisés au cœur de FastAPI). (*)
Les benchmarks indépendants de TechEmpower montrent que les applications **FastAPI** exécutées avec Uvicorn sont <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">lun des frameworks Python les plus rapides disponibles</a>, juste en dessous de Starlette et Uvicorn eux-mêmes (utilisés en interne par FastAPI).
Mais en prêtant attention aux tests de performance et aux comparaisons, il faut tenir compte de ce qu'il suit.
Mais lorsque vous consultez des benchmarks et des comparaisons, vous devez garder ce qui suit à lesprit.
## Tests de performance et rapidité
## Benchmarks et vitesse { #benchmarks-and-speed }
Lorsque vous vérifiez les tests de performance, il est commun de voir plusieurs outils de différents types comparés comme équivalents.
Lorsque vous consultez les benchmarks, il est courant de voir plusieurs outils de types différents comparés comme sils étaient équivalents.
En particulier, on voit Uvicorn, Starlette et FastAPI comparés (parmi de nombreux autres outils).
En particulier, de voir Uvicorn, Starlette et FastAPI comparés ensemble (parmi de nombreux autres outils).
Plus le problème résolu par un outil est simple, mieux seront les performances obtenues. Et la plupart des tests de performance ne prennent pas en compte les fonctionnalités additionnelles fournies par les outils.
Plus le problème résolu par loutil est simple, meilleures seront les performances quil obtiendra. Et la plupart des benchmarks ne testent pas les fonctionnalités supplémentaires fournies par loutil.
La hiérarchie est la suivante :
* **Uvicorn** : un serveur ASGI
* **Starlette** : (utilise Uvicorn) un micro-framework web
* **FastAPI**: (utilise Starlette) un micro-framework pour API disposant de fonctionnalités additionnelles pour la création d'API, avec la validation des données, etc.
* **Starlette** : (utilise Uvicorn) un microframework web
* **FastAPI** : (utilise Starlette) un microframework dAPI avec plusieurs fonctionnalités supplémentaires pour construire des API, avec la validation des données, etc.
* **Uvicorn** :
* A les meilleures performances, étant donné qu'il n'a pas beaucoup de code mis-à-part le serveur en lui-même.
* On n'écrit pas une application avec uniquement Uvicorn. Cela signifie que le code devrait inclure plus ou moins, au minimum, tout le code offert par Starlette (ou **FastAPI**). Et si on fait cela, l'application finale apportera les mêmes complications que si on avait utilisé un framework et que l'on avait minimisé la quantité de code et de bugs.
* Si on compare Uvicorn, il faut le comparer à d'autre applications de serveurs comme Daphne, Hypercorn, uWSGI, etc.
* Aura les meilleures performances, car il na pas beaucoup de code supplémentaire en dehors du serveur lui-même.
* Vous nécririez pas une application directement en Uvicorn. Cela signifierait que votre code devrait inclure plus ou moins, au minimum, tout le code fourni par Starlette (ou **FastAPI**). Et si vous faisiez cela, votre application finale aurait la même surcharge que si vous aviez utilisé un framework, tout en minimisant le code de votre application et les bugs.
* Si vous comparez Uvicorn, comparez-le à Daphne, Hypercorn, uWSGI, etc. Des serveurs dapplications.
* **Starlette** :
* A les seconde meilleures performances après Uvicorn. Starlette utilise en réalité Uvicorn. De ce fait, il ne peut quêtre plus "lent" qu'Uvicorn car il requiert l'exécution de plus de code.
* Cependant il nous apporte les outils pour construire une application web simple, avec un routage basé sur des chemins, etc.
* Si on compare Starlette, il faut le comparer à d'autres frameworks web (ou micorframework) comme Sanic, Flask, Django, etc.
* Aura les performances suivantes, après Uvicorn. En fait, Starlette utilise Uvicorn pour sexécuter. Donc, il ne peut probablement que devenir « plus lent » quUvicorn, en devant exécuter plus de code.
* Mais il vous fournit les outils pour construire des applications web simples, avec du routage basé sur des chemins, etc.
* Si vous comparez Starlette, comparez-le à Sanic, Flask, Django, etc. Des frameworks web (ou microframeworks).
* **FastAPI** :
* Comme Starlette, FastAPI utilise Uvicorn et ne peut donc pas être plus rapide que ce dernier.
* FastAPI apporte des fonctionnalités supplémentaires à Starlette. Des fonctionnalités qui sont nécessaires presque systématiquement lors de la création d'une API, comme la validation des données, la sérialisation. En utilisant FastAPI, on obtient une documentation automatiquement (qui ne requiert aucune manipulation pour être mise en place).
* Si on n'utilisait pas FastAPI mais directement Starlette (ou un outil équivalent comme Sanic, Flask, Responder, etc) il faudrait implémenter la validation des données et la sérialisation par nous-même. Le résultat serait donc le même dans les deux cas mais du travail supplémentaire serait à réaliser avec Starlette, surtout en considérant que la validation des données et la sérialisation représentent la plus grande quantité de code à écrire dans une application.
* De ce fait, en utilisant FastAPI on minimise le temps de développement, les bugs, le nombre de lignes de code, et on obtient les mêmes performances (si ce n'est de meilleurs performances) que l'on aurait pu avoir sans ce framework (en ayant à implémenter de nombreuses fonctionnalités importantes par nous-mêmes).
* Si on compare FastAPI, il faut le comparer à d'autres frameworks web (ou ensemble d'outils) qui fournissent la validation des données, la sérialisation et la documentation, comme Flask-apispec, NestJS, Molten, etc.
* De la même manière que Starlette utilise Uvicorn et ne peut pas être plus rapide que lui, **FastAPI** utilise Starlette, donc il ne peut pas être plus rapide que lui.
* FastAPI fournit plus de fonctionnalités par-dessus Starlette. Des fonctionnalités dont vous avez presque toujours besoin lorsque vous construisez des API, comme la validation des données et la sérialisation. Et en lutilisant, vous obtenez de la documentation automatique gratuitement (la documentation automatique najoute même pas de surcharge à lexécution des applications, elle est générée au démarrage).
* Si vous nutilisiez pas FastAPI et utilisiez Starlette directement (ou un autre outil, comme Sanic, Flask, Responder, etc.), vous devriez implémenter vous-même toute la validation des données et la sérialisation. Ainsi, votre application finale aurait toujours la même surcharge que si elle avait été construite en utilisant FastAPI. Et dans de nombreux cas, cette validation des données et cette sérialisation représentent la plus grande quantité de code écrite dans les applications.
* Ainsi, en utilisant FastAPI, vous économisez du temps de développement, des bugs, des lignes de code, et vous obtiendriez probablement les mêmes performances (ou de meilleures) que si vous ne lutilisiez pas (puisque vous devriez tout implémenter dans votre code).
* Si vous comparez FastAPI, comparez-le à un framework dapplication web (ou à un ensemble doutils) qui fournit la validation des données, la sérialisation et la documentation, comme Flask-apispec, NestJS, Molten, etc. Des frameworks avec validation automatique intégrée des données, sérialisation et documentation.

View File

@@ -1,74 +1,151 @@
# Déployer avec Docker
# FastAPI dans des conteneurs - Docker { #fastapi-in-containers-docker }
Dans cette section, vous verrez des instructions et des liens vers des guides pour savoir comment :
Lors du déploiement dapplications FastAPI, une approche courante consiste à construire une **image de conteneur Linux**. Cela se fait généralement avec <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Vous pouvez ensuite déployer cette image de conteneur de lune de plusieurs manières possibles.
* Faire de votre application **FastAPI** une image/conteneur Docker avec une performance maximale. En environ **5 min**.
* (Optionnellement) comprendre ce que vous, en tant que développeur, devez savoir sur HTTPS.
* Configurer un cluster en mode Docker Swarm avec HTTPS automatique, même sur un simple serveur à 5 dollars US/mois. En environ **20 min**.
* Générer et déployer une application **FastAPI** complète, en utilisant votre cluster Docker Swarm, avec HTTPS, etc. En environ **10 min**.
Vous pouvez utiliser <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a> pour le déploiement. Il présente plusieurs avantages comme la sécurité, la réplicabilité, la simplicité de développement, etc.
Si vous utilisez Docker, vous pouvez utiliser l'image Docker officielle :
## <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>
Cette image est dotée d'un mécanisme d'"auto-tuning", de sorte qu'il vous suffit d'ajouter votre code pour obtenir automatiquement des performances très élevées. Et sans faire de sacrifices.
Mais vous pouvez toujours changer et mettre à jour toutes les configurations avec des variables d'environnement ou des fichiers de configuration.
Lutilisation de conteneurs Linux présente plusieurs avantages, notamment la **sécurité**, la **réplicabilité**, la **simplicité**, et dautres.
/// tip | Astuce
Pour voir toutes les configurations et options, rendez-vous sur la page de l'image Docker : <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
Vous êtes pressé(e) et vous connaissez déjà tout ça ? Allez directement au [`Dockerfile` ci-dessous 👇](#build-a-docker-image-for-fastapi).
///
## Créer un `Dockerfile`
* Allez dans le répertoire de votre projet.
* Créez un `Dockerfile` avec :
<details>
<summary>Aperçu du Dockerfile 👀</summary>
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
FROM python:3.9
COPY ./app /app
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
# If running behind a proxy like Nginx or Traefik add --proxy-headers
# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
```
### Applications plus larges
</details>
Si vous avez suivi la section sur la création d' [Applications avec plusieurs fichiers](../tutorial/bigger-applications.md){.internal-link target=_blank}, votre `Dockerfile` pourrait ressembler à ceci :
## Quest-ce quun conteneur { #what-is-a-container }
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
Les conteneurs (principalement les conteneurs Linux) sont une manière très **légère** dempaqueter des applications, y compris toutes leurs dépendances et les fichiers nécessaires, tout en les gardant isolées des autres conteneurs (dautres applications ou composants) sur le même système.
COPY ./app /app/app
Les conteneurs Linux sexécutent en utilisant le même kernel Linux que lhôte (machine, machine virtuelle, serveur cloud, etc.). Cela signifie simplement quils sont très légers (comparés à des machines virtuelles complètes émulant un système dexploitation entier).
Ainsi, les conteneurs consomment **peu de ressources**, une quantité comparable à lexécution directe des processus (une machine virtuelle consommerait bien plus).
Les conteneurs ont aussi leurs propres processus en cours dexécution **isolés** (généralement un seul processus), leur système de fichiers et leur réseau, ce qui simplifie le déploiement, la sécurité, le développement, etc.
## Quest-ce quune image de conteneur { #what-is-a-container-image }
Un **conteneur** sexécute à partir dune **image de conteneur**.
Une image de conteneur est une version **statique** de tous les fichiers, des variables denvironnement, et de la commande/du programme par défaut qui doivent être présents dans un conteneur. **Statique** signifie ici que l**image** de conteneur ne sexécute pas, elle nest pas lancée, ce sont uniquement les fichiers empaquetés et les métadonnées.
Contrairement à une «image de conteneur» qui correspond au contenu statique stocké, un «conteneur» désigne généralement linstance en cours dexécution, la chose qui est **exécutée**.
Quand le **conteneur** est démarré et sexécute (démarré depuis une **image de conteneur**), il peut créer ou modifier des fichiers, des variables denvironnement, etc. Ces changements nexisteront que dans ce conteneur, et ne persisteront pas dans limage de conteneur sous-jacente (ils ne seront pas enregistrés sur disque).
Une image de conteneur est comparable au fichier et au contenu dun **programme**, par exemple `python` et un fichier `main.py`.
Et le **conteneur** lui-même (par opposition à l**image de conteneur**) est linstance réellement exécutée de limage, comparable à un **processus**. En fait, un conteneur ne sexécute que lorsquil a un **processus en cours dexécution** (et normalement cest un seul processus). Le conteneur sarrête lorsquil ny a plus de processus en cours dexécution à lintérieur.
## Images de conteneur { #container-images }
Docker a été lun des principaux outils pour créer et gérer les **images de conteneur** et les **conteneurs**.
Et il existe un <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> public avec des **images de conteneur officielles** préfabriquées pour de nombreux outils, environnements, bases de données, et applications.
Par exemple, il existe une <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">image Python</a> officielle.
Et il existe beaucoup dautres images pour différentes choses comme des bases de données, par exemple pour :
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>, etc.
En utilisant une image de conteneur préfabriquée, il est très facile de **combiner** et dutiliser différents outils. Par exemple, pour tester une nouvelle base de données. Dans la plupart des cas, vous pouvez utiliser les **images officielles** et simplement les configurer via des variables denvironnement.
De cette façon, dans de nombreux cas, vous pouvez apprendre les conteneurs et Docker et réutiliser ces connaissances avec beaucoup doutils et de composants différents.
Ainsi, vous exécuteriez **plusieurs conteneurs** avec différentes choses, comme une base de données, une application Python, un serveur web avec une application frontend React, et vous les connecteriez entre eux via leur réseau interne.
Tous les systèmes de gestion de conteneurs (comme Docker ou Kubernetes) intègrent ces fonctionnalités réseau.
## Conteneurs et processus { #containers-and-processes }
Une **image de conteneur** inclut normalement dans ses métadonnées le programme ou la commande par défaut à exécuter lorsque le **conteneur** est démarré, ainsi que les paramètres à passer à ce programme. Très similaire à ce que ce serait en ligne de commande.
Quand un **conteneur** est démarré, il exécutera cette commande/ce programme (bien que vous puissiez loutrepasser et lui faire exécuter une commande/un programme différent).
Un conteneur sexécute tant que le **processus principal** (commande ou programme) sexécute.
Un conteneur a normalement un **seul processus**, mais il est aussi possible de démarrer des sous-processus depuis le processus principal, et ainsi vous aurez **plusieurs processus** dans le même conteneur.
Mais il nest pas possible davoir un conteneur en cours dexécution sans **au moins un processus en cours dexécution**. Si le processus principal sarrête, le conteneur sarrête.
## Construire une image Docker pour FastAPI { #build-a-docker-image-for-fastapi }
Ok, construisons quelque chose maintenant ! 🚀
Je vais vous montrer comment construire une **image Docker** pour FastAPI **à partir de zéro**, sur la base de l**image Python officielle**.
Cest ce que vous voudrez faire dans **la plupart des cas**, par exemple :
* Utiliser **Kubernetes** ou des outils similaires
* Exécuter sur un **Raspberry Pi**
* Utiliser un service cloud qui exécute une image de conteneur pour vous, etc.
### Exigences de packages { #package-requirements }
Vous aurez normalement les **exigences de packages** de votre application dans un fichier.
Cela dépendra principalement de loutil que vous utilisez pour **installer** ces exigences.
La manière la plus courante de le faire est davoir un fichier `requirements.txt` avec les noms de packages et leurs versions, un par ligne.
Vous utiliseriez bien sûr les mêmes idées que celles présentées dans [À propos des versions de FastAPI](versions.md){.internal-link target=_blank} pour définir les plages de versions.
Par exemple, votre `requirements.txt` pourrait ressembler à :
```
fastapi[standard]>=0.113.0,<0.114.0
pydantic>=2.7.0,<3.0.0
```
### Raspberry Pi et autres architectures
Et vous installeriez normalement ces dépendances de packages avec `pip`, par exemple :
Si vous utilisez Docker sur un Raspberry Pi (qui a un processeur ARM) ou toute autre architecture, vous pouvez créer un `Dockerfile` à partir de zéro, basé sur une image de base Python (qui est multi-architecture) et utiliser Uvicorn seul.
<div class="termy">
Dans ce cas, votre `Dockerfile` pourrait ressembler à ceci :
```Dockerfile
FROM python:3.7
RUN pip install fastapi uvicorn
EXPOSE 80
COPY ./app /app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```console
$ pip install -r requirements.txt
---> 100%
Successfully installed fastapi pydantic
```
## Créer le code **FastAPI**.
</div>
* Créer un répertoire `app` et y entrer.
* Créez un fichier `main.py` avec :
/// info
Il existe dautres formats et outils pour définir et installer des dépendances de packages.
///
### Créer le code **FastAPI** { #create-the-fastapi-code }
* Créez un répertoire `app` et entrez dedans.
* Créez un fichier vide `__init__.py`.
* Créez un fichier `main.py` avec :
```Python
from typing import Optional
from typing import Union
from fastapi import FastAPI
@@ -81,20 +158,166 @@ def read_root():
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
* Vous devriez maintenant avoir une structure de répertoire telle que :
### Dockerfile { #dockerfile }
Maintenant, dans le même répertoire de projet, créez un fichier `Dockerfile` avec :
```{ .dockerfile .annotate }
# (1)!
FROM python:3.9
# (2)!
WORKDIR /code
# (3)!
COPY ./requirements.txt /code/requirements.txt
# (4)!
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (5)!
COPY ./app /code/app
# (6)!
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
```
1. Partir de limage de base Python officielle.
2. Définir le répertoire de travail courant sur `/code`.
Cest là que nous mettrons le fichier `requirements.txt` et le répertoire `app`.
3. Copier le fichier contenant les exigences dans le répertoire `/code`.
Copier **uniquement** le fichier contenant les exigences dabord, pas le reste du code.
Comme ce fichier **ne change pas souvent**, Docker le détectera et utilisera le **cache** pour cette étape, permettant dutiliser le cache pour létape suivante aussi.
4. Installer les dépendances de packages du fichier dexigences.
Loption `--no-cache-dir` indique à `pip` de ne pas enregistrer localement les packages téléchargés, car cela ne sert que si `pip` devait être relancé pour installer les mêmes packages, mais ce nest pas le cas lorsquon travaille avec des conteneurs.
/// note | Remarque
`--no-cache-dir` est uniquement lié à `pip`, il na rien à voir avec Docker ou les conteneurs.
///
Loption `--upgrade` indique à `pip` de mettre à niveau les packages sils sont déjà installés.
Comme létape précédente de copie du fichier pourrait être détectée par le **cache Docker**, cette étape va aussi **utiliser le cache Docker** lorsquil est disponible.
Utiliser le cache à cette étape va vous **faire gagner** beaucoup de **temps** lorsque vous reconstruisez limage encore et encore pendant le développement, au lieu de **télécharger et installer** toutes les dépendances **à chaque fois**.
5. Copier le répertoire `./app` dans le répertoire `/code`.
Comme cela contient tout le code, qui est ce qui **change le plus fréquemment**, le **cache** Docker ne sera pas facilement utilisé pour cette étape ou pour les **étapes suivantes**.
Il est donc important de placer cela **près de la fin** du `Dockerfile`, pour optimiser les temps de build de limage de conteneur.
6. Définir la **commande** pour utiliser `fastapi run`, qui utilise Uvicorn en dessous.
`CMD` prend une liste de chaînes de caractères, chacune de ces chaînes correspond à ce que vous taperiez en ligne de commande, séparé par des espaces.
Cette commande sera exécutée depuis le **répertoire de travail courant**, le même répertoire `/code` que vous avez défini plus haut avec `WORKDIR /code`.
/// tip | Astuce
Passez en revue ce que fait chaque ligne en cliquant sur chaque bulle numérotée dans le code. 👆
///
/// warning | Alertes
Vous devez vous assurer dutiliser **toujours** la **forme exec** de linstruction `CMD`, comme expliqué ci-dessous.
///
#### Utiliser `CMD` - forme exec { #use-cmd-exec-form }
Linstruction Docker <a href="https://docs.docker.com/reference/dockerfile/#cmd" class="external-link" target="_blank">`CMD`</a> peut sécrire sous deux formes :
✅ Forme **exec** :
```Dockerfile
# ✅ Do this
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
```
⛔️ Forme **shell** :
```Dockerfile
# ⛔️ Don't do this
CMD fastapi run app/main.py --port 80
```
Vous devez vous assurer de toujours utiliser la forme **exec** pour garantir que FastAPI puisse sarrêter proprement et que les [événements de lifespan](../advanced/events.md){.internal-link target=_blank} soient déclenchés.
Vous pouvez en lire plus à ce sujet dans la <a href="https://docs.docker.com/reference/dockerfile/#shell-and-exec-form" class="external-link" target="_blank">documentation Docker sur les formes shell et exec</a>.
Cela peut être assez visible lors de lutilisation de `docker compose`. Consultez cette section de la FAQ Docker Compose pour plus de détails techniques : <a href="https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop" class="external-link" target="_blank">Why do my services take 10 seconds to recreate or stop?</a>.
#### Structure de répertoires { #directory-structure }
Vous devriez maintenant avoir une structure de répertoires comme :
```
.
├── app
│   ├── __init__.py
│ └── main.py
── Dockerfile
── Dockerfile
└── requirements.txt
```
## Construire l'image Docker
#### Derrière un proxy de terminaison TLS { #behind-a-tls-termination-proxy }
Si vous exécutez votre conteneur derrière un proxy de terminaison TLS (load balancer) comme Nginx ou Traefik, ajoutez loption `--proxy-headers` ; cela indiquera à Uvicorn (via la CLI FastAPI) de faire confiance aux en-têtes envoyés par ce proxy lui indiquant que lapplication sexécute derrière HTTPS, etc.
```Dockerfile
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]
```
#### Cache Docker { #docker-cache }
Il y a une astuce importante dans ce `Dockerfile` : nous copions dabord **le fichier des dépendances seul**, pas le reste du code. Je vais vous expliquer pourquoi.
```Dockerfile
COPY ./requirements.txt /code/requirements.txt
```
Docker et dautres outils **construisent** ces images de conteneur **de manière incrémentale**, en ajoutant **une couche par-dessus lautre**, en commençant par le haut du `Dockerfile` et en ajoutant tous les fichiers créés par chacune des instructions du `Dockerfile`.
Docker et des outils similaires utilisent aussi un **cache interne** lors de la construction de limage : si un fichier na pas changé depuis la dernière construction de limage de conteneur, alors il va **réutiliser la même couche** créée la dernière fois, au lieu de copier le fichier à nouveau et de créer une nouvelle couche à partir de zéro.
Le simple fait déviter la copie de fichiers naméliore pas forcément beaucoup les choses, mais comme le cache a été utilisé pour cette étape, il peut **être utilisé pour létape suivante**. Par exemple, il pourrait être utilisé pour linstruction qui installe les dépendances avec :
```Dockerfile
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
```
Le fichier des exigences de packages **ne changera pas fréquemment**. Donc, en ne copiant que ce fichier, Docker pourra **utiliser le cache** pour cette étape.
Et ensuite, Docker pourra **utiliser le cache pour létape suivante** qui télécharge et installe ces dépendances. Et cest là que nous **gagnons beaucoup de temps**. ✨ ... et évitons lennui dattendre. 😪😆
Le téléchargement et linstallation des dépendances de packages **peuvent prendre des minutes**, mais utiliser le **cache** **prendrait au maximum quelques secondes**.
Et comme vous reconstruiriez limage de conteneur encore et encore pendant le développement pour vérifier que vos modifications de code fonctionnent, cela vous ferait gagner beaucoup de temps cumulé.
Ensuite, près de la fin du `Dockerfile`, nous copions tout le code. Comme cest ce qui **change le plus fréquemment**, nous le mettons près de la fin, car presque toujours, tout ce qui vient après cette étape ne pourra pas utiliser le cache.
```Dockerfile
COPY ./app /code/app
```
### Construire limage Docker { #build-the-docker-image }
Maintenant que tous les fichiers sont en place, construisons limage de conteneur.
* Allez dans le répertoire du projet (dans lequel se trouve votre `Dockerfile`, contenant votre répertoire `app`).
* Construisez votre image FastAPI :
@@ -109,9 +332,17 @@ $ docker build -t myimage .
</div>
## Démarrer le conteneur Docker
/// tip | Astuce
* Exécutez un conteneur basé sur votre image :
Remarquez le `.` à la fin, il est équivalent à `./` : il indique à Docker le répertoire à utiliser pour construire limage de conteneur.
Dans ce cas, cest le même répertoire courant (`.`).
///
### Démarrer le conteneur Docker { #start-the-docker-container }
* Exécutez un conteneur basé sur votre image :
<div class="termy">
@@ -121,65 +352,269 @@ $ docker run -d --name mycontainer -p 80:80 myimage
</div>
Vous disposez maintenant d'un serveur FastAPI optimisé dans un conteneur Docker. Configuré automatiquement pour votre
serveur actuel (et le nombre de cœurs du CPU).
## Vérifier { #check-it }
## Vérifier
Vous devriez pouvoir le vérifier via lURL de votre conteneur Docker, par exemple : <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> ou <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (ou équivalent, en utilisant votre hôte Docker).
Vous devriez pouvoir accéder à votre application via l'URL de votre conteneur Docker, par exemple : <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> ou <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (ou équivalent, en utilisant votre hôte Docker).
Vous verrez quelque chose comme :
Vous verrez quelque chose comme :
```JSON
{"item_id": 5, "q": "somequery"}
```
## Documentation interactive de l'API
## Documentation interactive de lAPI { #interactive-api-docs }
Vous pouvez maintenant visiter <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> ou <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (ou équivalent, en utilisant votre hôte Docker).
Vous pouvez maintenant aller sur <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> ou <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (ou équivalent, en utilisant votre hôte Docker).
Vous verrez la documentation interactive automatique de l'API (fournie par <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>) :
Vous verrez la documentation interactive automatique de lAPI (fournie par <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>) :
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
## Documentation de l'API alternative
## Documentation de lAPI alternative { #alternative-api-docs }
Et vous pouvez également aller sur <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> ou <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (ou équivalent, en utilisant votre hôte Docker).
Vous verrez la documentation automatique alternative (fournie par <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>) :
Vous verrez la documentation automatique alternative (fournie par <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>) :
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Traefik
## Construire une image Docker avec un FastAPI dans un seul fichier { #build-a-docker-image-with-a-single-file-fastapi }
<a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a> est un reverse proxy/load balancer
haute performance. Il peut faire office de "Proxy de terminaison TLS" (entre autres fonctionnalités).
Si votre FastAPI est un seul fichier, par exemple `main.py` sans répertoire `./app`, votre structure de fichiers pourrait ressembler à ceci :
Il est intégré à Let's Encrypt. Ainsi, il peut gérer toutes les parties HTTPS, y compris l'acquisition et le renouvellement des certificats.
```
.
├── Dockerfile
├── main.py
└── requirements.txt
```
Il est également intégré à Docker. Ainsi, vous pouvez déclarer vos domaines dans les configurations de chaque application et faire en sorte qu'elles lisent ces configurations, génèrent les certificats HTTPS et servent via HTTPS à votre application automatiquement, sans nécessiter aucune modification de leurs configurations.
Ensuite, vous nauriez quà modifier les chemins correspondants pour copier le fichier dans le `Dockerfile` :
```{ .dockerfile .annotate hl_lines="10 13" }
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (1)!
COPY ./main.py /code/
# (2)!
CMD ["fastapi", "run", "main.py", "--port", "80"]
```
1. Copier le fichier `main.py` directement dans le répertoire `/code` (sans aucun répertoire `./app`).
2. Utiliser `fastapi run` pour servir votre application dans le fichier unique `main.py`.
Quand vous passez le fichier à `fastapi run`, il détectera automatiquement quil sagit dun fichier unique et non dune partie dun package, et saura comment limporter et servir votre application FastAPI. 😎
## Concepts de déploiement { #deployment-concepts }
Parlons à nouveau de certains des mêmes [concepts de déploiement](concepts.md){.internal-link target=_blank} en termes de conteneurs.
Les conteneurs sont principalement un outil pour simplifier le processus de **construction et de déploiement** dune application, mais ils nimposent pas une approche particulière pour gérer ces **concepts de déploiement**, et il existe plusieurs stratégies possibles.
La **bonne nouvelle**, cest quavec chaque stratégie différente il existe un moyen de couvrir tous les concepts de déploiement. 🎉
Passons en revue ces **concepts de déploiement** en termes de conteneurs :
* HTTPS
* Exécution au démarrage
* Redémarrages
* Réplication (le nombre de processus en cours dexécution)
* Mémoire
* Étapes préalables avant de démarrer
## HTTPS { #https }
Si nous nous concentrons uniquement sur l**image de conteneur** dune application FastAPI (et plus tard sur le **conteneur** en cours dexécution), HTTPS serait normalement géré **en externe** par un autre outil.
Cela pourrait être un autre conteneur, par exemple avec <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, gérant **HTTPS** et lacquisition **automatique** de **certificats**.
/// tip | Astuce
Traefik dispose dintégrations avec Docker, Kubernetes et dautres, il est donc très simple à mettre en place et à configurer pour HTTPS avec vos conteneurs.
///
Alternativement, HTTPS pourrait être géré par un fournisseur cloud comme lun de ses services (tout en exécutant lapplication dans un conteneur).
## Exécution au démarrage et redémarrages { #running-on-startup-and-restarts }
Il y a normalement un autre outil chargé de **démarrer et exécuter** votre conteneur.
Cela pourrait être **Docker** directement, **Docker Compose**, **Kubernetes**, un **service cloud**, etc.
Dans la plupart (ou tous) les cas, il existe une option simple pour activer lexécution du conteneur au démarrage et activer les redémarrages en cas déchec. Par exemple, dans Docker, cest loption en ligne de commande `--restart`.
Sans utiliser de conteneurs, faire exécuter des applications au démarrage et avec redémarrages peut être contraignant et difficile. Mais en **travaillant avec des conteneurs**, dans la plupart des cas cette fonctionnalité est incluse par défaut. ✨
## Réplication - Nombre de processus { #replication-number-of-processes }
Si vous avez un <abbr title="Un groupe de machines configurées pour être connectées et travailler ensemble dune certaine façon.">cluster</abbr> de machines avec **Kubernetes**, Docker Swarm Mode, Nomad, ou un autre système complexe similaire pour gérer des conteneurs distribués sur plusieurs machines, alors vous voudrez probablement **gérer la réplication** au **niveau du cluster** au lieu dutiliser un **gestionnaire de processus** (comme Uvicorn avec des workers) dans chaque conteneur.
Un de ces systèmes de gestion de conteneurs distribués comme Kubernetes dispose normalement dun moyen intégré de gérer la **réplication des conteneurs** tout en prenant en charge l**équilibrage de charge** pour les requêtes entrantes. Le tout au **niveau du cluster**.
Dans ces cas, vous voudrez probablement construire une **image Docker à partir de zéro** comme [expliqué ci-dessus](#dockerfile), installer vos dépendances, et exécuter **un seul processus Uvicorn** au lieu dutiliser plusieurs workers Uvicorn.
### Load Balancer { #load-balancer }
Lors de lutilisation de conteneurs, vous aurez normalement un composant **à lécoute sur le port principal**. Cela pourrait éventuellement être un autre conteneur qui est aussi un **proxy de terminaison TLS** pour gérer **HTTPS** ou un outil similaire.
Comme ce composant prendrait la **charge** des requêtes et la distribuerait entre les workers dune manière (espérons-le) **équilibrée**, on lappelle aussi communément un **Load Balancer**.
/// tip | Astuce
Le même composant de **proxy de terminaison TLS** utilisé pour HTTPS serait probablement aussi un **Load Balancer**.
///
Et en travaillant avec des conteneurs, le même système que vous utilisez pour les démarrer et les gérer aura déjà des outils internes pour transmettre la **communication réseau** (par exemple les requêtes HTTP) depuis ce **load balancer** (qui pourrait aussi être un **proxy de terminaison TLS**) vers le(s) conteneur(s) avec votre application.
### Un load balancer - Plusieurs conteneurs workers { #one-load-balancer-multiple-worker-containers }
En travaillant avec **Kubernetes** ou des systèmes distribués de gestion de conteneurs similaires, utiliser leurs mécanismes réseau internes permettrait au **load balancer** unique qui écoute sur le **port** principal de transmettre la communication (les requêtes) à potentiellement **plusieurs conteneurs** exécutant votre application.
Chacun de ces conteneurs exécutant votre application aurait normalement **un seul processus** (par exemple un processus Uvicorn exécutant votre application FastAPI). Ce seraient tous des **conteneurs identiques**, exécutant la même chose, mais chacun avec son propre processus, sa mémoire, etc. Ainsi, vous profiteriez de la **parallélisation** sur **différents cœurs** du CPU, ou même sur **différentes machines**.
Et le système de conteneurs distribués avec le **load balancer** **distribuerait les requêtes** à chacun des conteneurs avec votre application **à tour de rôle**. Ainsi, chaque requête pourrait être traitée par lun des multiples **conteneurs répliqués** exécutant votre application.
Et normalement ce **load balancer** serait capable de gérer des requêtes allant vers *dautres* applications dans votre cluster (par exemple vers un domaine différent, ou sous un préfixe de chemin dURL différent), et transmettrait cette communication aux bons conteneurs pour *cette autre* application sexécutant dans votre cluster.
### Un processus par conteneur { #one-process-per-container }
Dans ce type de scénario, vous voudrez probablement avoir **un seul processus (Uvicorn) par conteneur**, puisque vous gérez déjà la réplication au niveau du cluster.
Donc, dans ce cas, vous **ne** voudrez **pas** avoir plusieurs workers dans le conteneur, par exemple avec loption en ligne de commande `--workers`. Vous voudrez avoir un **seul processus Uvicorn** par conteneur (mais probablement plusieurs conteneurs).
Avoir un autre gestionnaire de processus à lintérieur du conteneur (comme ce serait le cas avec plusieurs workers) ne ferait quajouter une **complexité inutile** que vous gérez très probablement déjà avec votre système de cluster.
### Conteneurs avec plusieurs processus et cas particuliers { #containers-with-multiple-processes-and-special-cases }
Bien sûr, il existe des **cas particuliers** où vous pourriez vouloir avoir **un conteneur** avec plusieurs **processus workers Uvicorn** à lintérieur.
Dans ces cas, vous pouvez utiliser loption en ligne de commande `--workers` pour définir le nombre de workers que vous souhaitez exécuter :
```{ .dockerfile .annotate }
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
# (1)!
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"]
```
1. Ici, nous utilisons loption en ligne de commande `--workers` pour définir le nombre de workers à 4.
Voici quelques exemples où cela pourrait avoir du sens :
#### Une application simple { #a-simple-app }
Vous pourriez vouloir un gestionnaire de processus dans le conteneur si votre application est **suffisamment simple** pour pouvoir sexécuter sur un **seul serveur**, pas un cluster.
#### Docker Compose { #docker-compose }
Vous pourriez déployer sur un **seul serveur** (pas un cluster) avec **Docker Compose**, et vous nauriez donc pas un moyen simple de gérer la réplication des conteneurs (avec Docker Compose) tout en préservant le réseau partagé et l**équilibrage de charge**.
Vous pourriez alors vouloir avoir **un seul conteneur** avec un **gestionnaire de processus** démarrant **plusieurs processus worker** à lintérieur.
---
Avec ces informations et ces outils, passez à la section suivante pour tout combiner.
Le point principal est que **rien** de tout cela nest une **règle gravée dans le marbre** que vous devez suivre aveuglément. Vous pouvez utiliser ces idées pour **évaluer votre propre cas dusage** et décider quelle est la meilleure approche pour votre système, en examinant comment gérer les concepts de :
## Cluster en mode Docker Swarm avec Traefik et HTTPS
* Sécurité - HTTPS
* Exécution au démarrage
* Redémarrages
* Réplication (le nombre de processus en cours dexécution)
* Mémoire
* Étapes préalables avant de démarrer
Vous pouvez avoir un cluster en mode Docker Swarm configuré en quelques minutes (environ 20 min) avec un processus Traefik principal gérant HTTPS (y compris l'acquisition et le renouvellement des certificats).
## Mémoire { #memory }
En utilisant le mode Docker Swarm, vous pouvez commencer par un "cluster" d'une seule machine (il peut même s'agir
d'un serveur à 5 USD/mois) et ensuite vous pouvez vous développer autant que vous le souhaitez en ajoutant d'autres serveurs.
Si vous exécutez **un seul processus par conteneur**, vous aurez une quantité de mémoire plus ou moins bien définie, stable, et limitée consommée par chacun de ces conteneurs (plus dun si vous les répliquez).
Pour configurer un cluster en mode Docker Swarm avec Traefik et la gestion de HTTPS, suivez ce guide :
Et ensuite, vous pouvez définir ces mêmes limites et exigences de mémoire dans vos configurations pour votre système de gestion de conteneurs (par exemple dans **Kubernetes**). Ainsi, il pourra **répliquer les conteneurs** sur les **machines disponibles** en tenant compte de la quantité de mémoire nécessaire et de la quantité disponible sur les machines du cluster.
### <a href="https://medium.com/@tiangolo/docker-swarm-mode-and-traefik-for-a-https-cluster-20328dba6232" class="external-link" target="_blank">Docker Swarm Mode et Traefik pour un cluster HTTPS</a>
Si votre application est **simple**, ce ne sera probablement **pas un problème**, et vous pourriez ne pas avoir besoin de spécifier des limites strictes de mémoire. Mais si vous **utilisez beaucoup de mémoire** (par exemple avec des modèles de **machine learning**), vous devriez vérifier la quantité de mémoire consommée et ajuster le **nombre de conteneurs** qui sexécutent sur **chaque machine** (et peut-être ajouter plus de machines à votre cluster).
### Déployer une application FastAPI
Si vous exécutez **plusieurs processus par conteneur**, vous devez vous assurer que le nombre de processus démarrés ne **consomme pas plus de mémoire** que ce qui est disponible.
La façon la plus simple de tout mettre en place, serait d'utiliser les [**Générateurs de projet FastAPI**](../project-generation.md){.internal-link target=_blank}.
## Étapes préalables avant de démarrer et conteneurs { #previous-steps-before-starting-and-containers }
Le génerateur de projet adéquat est conçu pour être intégré à ce cluster Docker Swarm avec Traefik et HTTPS décrit ci-dessus.
Si vous utilisez des conteneurs (par exemple Docker, Kubernetes), alors il existe deux approches principales que vous pouvez utiliser.
Vous pouvez générer un projet en 2 min environ.
### Plusieurs conteneurs { #multiple-containers }
Le projet généré a des instructions pour le déployer et le faire prend 2 min de plus.
Si vous avez **plusieurs conteneurs**, probablement chacun exécutant un **seul processus** (par exemple, dans un cluster **Kubernetes**), vous voudrez probablement avoir un **conteneur séparé** pour effectuer le travail des **étapes préalables** dans un conteneur unique, exécutant un seul processus, **avant** dexécuter les conteneurs workers répliqués.
/// info
Si vous utilisez Kubernetes, ce serait probablement un <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>.
///
Si, dans votre cas dusage, il ny a pas de problème à exécuter ces étapes préalables **plusieurs fois en parallèle** (par exemple si vous nexécutez pas des migrations de base de données, mais vérifiez simplement si la base de données est prête), alors vous pourriez aussi les placer dans chaque conteneur juste avant de démarrer le processus principal.
### Conteneur unique { #single-container }
Si vous avez une configuration simple, avec un **conteneur unique** qui démarre ensuite plusieurs **processus worker** (ou aussi juste un processus), alors vous pourriez exécuter ces étapes préalables dans le même conteneur, juste avant de démarrer le processus avec lapplication.
### Image Docker de base { #base-docker-image }
Il existait une image Docker FastAPI officielle : <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. Mais elle est maintenant dépréciée. ⛔️
Vous ne devriez probablement **pas** utiliser cette image Docker de base (ni une autre similaire).
Si vous utilisez **Kubernetes** (ou dautres) et que vous définissez déjà la **réplication** au niveau du cluster, avec plusieurs **conteneurs**. Dans ces cas, il vaut mieux **construire une image à partir de zéro** comme décrit ci-dessus : [Construire une image Docker pour FastAPI](#build-a-docker-image-for-fastapi).
Et si vous devez avoir plusieurs workers, vous pouvez simplement utiliser loption en ligne de commande `--workers`.
/// note | Détails techniques
Limage Docker a été créée à une époque où Uvicorn ne prenait pas en charge la gestion et le redémarrage des workers morts, il était donc nécessaire dutiliser Gunicorn avec Uvicorn, ce qui ajoutait pas mal de complexité, simplement pour que Gunicorn gère et redémarre les processus workers Uvicorn.
Mais maintenant quUvicorn (et la commande `fastapi`) prennent en charge lutilisation de `--workers`, il ny a aucune raison dutiliser une image Docker de base au lieu de construire la vôtre (cest à peu près la même quantité de code 😅).
///
## Déployer limage de conteneur { #deploy-the-container-image }
Après avoir une image de conteneur (Docker), il existe plusieurs façons de la déployer.
Par exemple :
* Avec **Docker Compose** sur un seul serveur
* Avec un cluster **Kubernetes**
* Avec un cluster en mode Docker Swarm
* Avec un autre outil comme Nomad
* Avec un service cloud qui prend votre image de conteneur et la déploie
## Image Docker avec `uv` { #docker-image-with-uv }
Si vous utilisez <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a> pour installer et gérer votre projet, vous pouvez suivre leur <a href="https://docs.astral.sh/uv/guides/integration/docker/" class="external-link" target="_blank">guide Docker uv</a>.
## Récapitulatif { #recap }
En utilisant des systèmes de conteneurs (par ex. avec **Docker** et **Kubernetes**), il devient assez simple de gérer tous les **concepts de déploiement** :
* HTTPS
* Exécution au démarrage
* Redémarrages
* Réplication (le nombre de processus en cours dexécution)
* Mémoire
* Étapes préalables avant de démarrer
Dans la plupart des cas, vous ne voudrez probablement pas utiliser une image de base, et vous voudrez plutôt **construire une image de conteneur à partir de zéro** basée sur limage Docker Python officielle.
En prenant soin de l**ordre** des instructions dans le `Dockerfile` et du **cache Docker**, vous pouvez **minimiser les temps de build**, afin de maximiser votre productivité (et déviter lennui). 😎

View File

@@ -1,56 +1,231 @@
# À propos de HTTPS
# À propos de HTTPS { #about-https }
Il est facile de penser que HTTPS peut simplement être "activé" ou non.
Il est facile de supposer que HTTPS est quelque chose qui est simplement « activé » ou non.
Mais c'est beaucoup plus complexe que cela.
Mais c'est bien plus complexe que cela.
/// tip
/// tip | Astuce
Si vous êtes pressé ou si cela ne vous intéresse pas, passez aux sections suivantes pour obtenir des instructions étape par étape afin de tout configurer avec différentes techniques.
Si vous êtes pressé ou si cela ne vous intéresse pas, continuez avec les sections suivantes pour obtenir des instructions étape par étape afin de tout configurer avec différentes techniques.
///
Pour apprendre les bases du HTTPS, du point de vue d'un utilisateur, consultez <a href="https://howhttps.works/"
class="external-link" target="_blank">https://howhttps.works/</a>.
Pour **apprendre les bases de HTTPS**, du point de vue d'un utilisateur, consultez <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>.
Maintenant, du point de vue d'un développeur, voici plusieurs choses à avoir en tête en pensant au HTTPS :
Maintenant, du point de vue d'un développeur, voici plusieurs choses à garder à l'esprit en pensant à HTTPS :
* Pour le HTTPS, le serveur a besoin de "certificats" générés par une tierce partie.
* Ces certificats sont en fait acquis auprès de la tierce partie, et non "générés".
* Les certificats ont une durée de vie.
* Ils expirent.
* Puis ils doivent être renouvelés et acquis à nouveau auprès de la tierce partie.
* Le cryptage de la connexion se fait au niveau du protocole TCP.
* C'est une couche en dessous de HTTP.
* Donc, le certificat et le traitement du cryptage sont faits avant HTTP.
* TCP ne connaît pas les "domaines", seulement les adresses IP.
* L'information sur le domaine spécifique demandé se trouve dans les données HTTP.
* Les certificats HTTPS "certifient" un certain domaine, mais le protocole et le cryptage se font au niveau TCP, avant de savoir quel domaine est traité.
* Par défaut, cela signifie que vous ne pouvez avoir qu'un seul certificat HTTPS par adresse IP.
* Quelle que soit la taille de votre serveur ou la taille de chacune des applications qu'il contient.
* Il existe cependant une solution à ce problème.
* Il existe une extension du protocole TLS (celui qui gère le cryptage au niveau TCP, avant HTTP) appelée <a
href="https://fr.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr
title="Server Name Indication (indication du nom du serveur)">SNI (indication du nom du serveur)</abbr></a>.
* Cette extension SNI permet à un seul serveur (avec une seule adresse IP) d'avoir plusieurs certificats HTTPS et de servir plusieurs domaines/applications HTTPS.
* Pour que cela fonctionne, un seul composant (programme) fonctionnant sur le serveur, écoutant sur l'adresse IP publique, doit avoir tous les certificats HTTPS du serveur.
* Après avoir obtenu une connexion sécurisée, le protocole de communication est toujours HTTP.
* Le contenu est crypté, même s'il est envoyé avec le protocole HTTP.
* Pour HTTPS, **le serveur** doit **avoir des « certificats »** générés par une **tierce partie**.
* Ces certificats sont en fait **acquis** auprès de la tierce partie, et non « générés ».
* Les certificats ont une **durée de vie**.
* Ils **expirent**.
* Et ensuite ils doivent être **renouvelés**, **acquis à nouveau** auprès de la tierce partie.
* Le chiffrement de la connexion se fait au **niveau TCP**.
* C'est une couche **en dessous de HTTP**.
* Donc, la gestion du **certificat et du chiffrement** est faite **avant HTTP**.
* **TCP ne connaît pas les « domaines »**. Seulement les adresses IP.
* L'information sur le **domaine spécifique** demandé se trouve dans les **données HTTP**.
* Les **certificats HTTPS** « certifient » un **certain domaine**, mais le protocole et le chiffrement se font au niveau TCP, **avant de savoir** quel domaine est traité.
* **Par défaut**, cela signifierait que vous ne pouvez avoir qu'**un seul certificat HTTPS par adresse IP**.
* Peu importe la taille de votre serveur ou à quel point chaque application que vous avez dessus peut être petite.
* Il existe cependant une **solution** à ce problème.
* Il existe une **extension** du protocole **TLS** (celui qui gère le chiffrement au niveau TCP, avant HTTP) appelée **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Server Name Indication - Indication du nom du serveur">SNI</abbr></a>**.
* Cette extension SNI permet à un seul serveur (avec une **seule adresse IP**) d'avoir **plusieurs certificats HTTPS** et de servir **plusieurs domaines/applications HTTPS**.
* Pour que cela fonctionne, un **seul** composant (programme) fonctionnant sur le serveur, écoutant sur l'**adresse IP publique**, doit avoir **tous les certificats HTTPS** du serveur.
* **Après** avoir obtenu une connexion sécurisée, le protocole de communication est **toujours HTTP**.
* Le contenu est **chiffré**, même s'il est envoyé avec le **protocole HTTP**.
Il est courant d'avoir un seul programme/serveur HTTP fonctionnant sur le serveur (la machine, l'hôte, etc.) et
gérant toutes les parties HTTPS : envoyer les requêtes HTTP décryptées à l'application HTTP réelle fonctionnant sur
le même serveur (dans ce cas, l'application **FastAPI**), prendre la réponse HTTP de l'application, la crypter en utilisant le certificat approprié et la renvoyer au client en utilisant HTTPS. Ce serveur est souvent appelé un <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">Proxy de terminaison TLS</a>.
Il est courant d'avoir **un seul programme/serveur HTTP** fonctionnant sur le serveur (la machine, l'hôte, etc.) et **gérant toutes les parties HTTPS** : recevoir les **requêtes HTTPS chiffrées**, envoyer les **requêtes HTTP déchiffrées** à l'application HTTP réelle fonctionnant sur le même serveur (l'application **FastAPI**, dans ce cas), prendre la **réponse HTTP** de l'application, la **chiffrer** en utilisant le **certificat HTTPS** approprié et la renvoyer au client en utilisant **HTTPS**. Ce serveur est souvent appelé un **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">Proxy de terminaison TLS</a>**.
## Let's Encrypt
Certaines des options que vous pouvez utiliser comme Proxy de terminaison TLS sont :
Avant Let's Encrypt, ces certificats HTTPS étaient vendus par des tiers de confiance.
* Traefik (qui peut aussi gérer les renouvellements de certificats)
* Caddy (qui peut aussi gérer les renouvellements de certificats)
* Nginx
* HAProxy
## Let's Encrypt { #lets-encrypt }
Avant Let's Encrypt, ces **certificats HTTPS** étaient vendus par des tiers de confiance.
Le processus d'acquisition d'un de ces certificats était auparavant lourd, nécessitait pas mal de paperasses et les certificats étaient assez chers.
Mais ensuite, <a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a> a été créé.
Mais ensuite **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>** a été créé.
Il s'agit d'un projet de la Fondation Linux. Il fournit des certificats HTTPS gratuitement. De manière automatisée. Ces certificats utilisent toutes les sécurités cryptographiques standard et ont une durée de vie courte (environ 3 mois), de sorte que la sécurité est en fait meilleure en raison de leur durée de vie réduite.
Il s'agit d'un projet de la Linux Foundation. Il fournit des **certificats HTTPS gratuitement**, de manière automatisée. Ces certificats utilisent toutes les sécurités cryptographiques standard, et ont une durée de vie courte (environ 3 mois), de sorte que la **sécurité est en fait meilleure** en raison de leur durée de vie réduite.
Les domaines sont vérifiés de manière sécurisée et les certificats sont générés automatiquement. Cela permet également d'automatiser le renouvellement de ces certificats.
L'idée est d'automatiser l'acquisition et le renouvellement de ces certificats, afin que vous puissiez disposer d'un HTTPS sécurisé, gratuitement et pour toujours.
L'idée est d'automatiser l'acquisition et le renouvellement de ces certificats, afin que vous puissiez disposer d'un **HTTPS sécurisé, gratuitement, pour toujours**.
## HTTPS pour les développeurs { #https-for-developers }
Voici un exemple de ce à quoi une API HTTPS pourrait ressembler, étape par étape, en faisant attention principalement aux idées importantes pour les développeurs.
### Nom de domaine { #domain-name }
Cela commencerait probablement par le fait que vous **acquérez** un **nom de domaine**. Ensuite, vous le configureriez dans un serveur DNS (éventuellement votre même fournisseur cloud).
Vous obtiendriez probablement un serveur cloud (une machine virtuelle) ou quelque chose de similaire, et il aurait une adresse IP publique <abbr title="That doesn't change - Qui ne change pas">fixed</abbr>.
Dans le(s) serveur(s) DNS vous configureriez un enregistrement (un « `A record` ») pour faire pointer **votre domaine** vers l'**adresse IP publique de votre serveur**.
Vous feriez probablement cela une seule fois, la première fois, lorsque vous mettez tout en place.
/// tip | Astuce
Cette partie « nom de domaine » se situe bien avant HTTPS, mais comme tout dépend du domaine et de l'adresse IP, cela vaut la peine de le mentionner ici.
///
### DNS { #dns }
Maintenant, concentrons-nous sur toutes les parties réellement liées à HTTPS.
D'abord, le navigateur vérifierait auprès des **serveurs DNS** quelle est l'**IP du domaine**, dans ce cas, `someapp.example.com`.
Les serveurs DNS diraient au navigateur d'utiliser une **adresse IP** spécifique. Ce serait l'adresse IP publique utilisée par votre serveur, que vous avez configurée dans les serveurs DNS.
<img src="/img/deployment/https/https01.drawio.svg">
### Début du handshake TLS { #tls-handshake-start }
Le navigateur communiquerait ensuite avec cette adresse IP sur le **port 443** (le port HTTPS).
La première partie de la communication consiste simplement à établir la connexion entre le client et le serveur et à décider des clés cryptographiques qu'ils utiliseront, etc.
<img src="/img/deployment/https/https02.drawio.svg">
Cette interaction entre le client et le serveur pour établir la connexion TLS s'appelle le **handshake TLS**.
### TLS avec l'extension SNI { #tls-with-sni-extension }
**Un seul process** sur le serveur peut écouter sur un **port** spécifique sur une **adresse IP** spécifique. Il peut y avoir d'autres process écoutant sur d'autres ports sur la même adresse IP, mais un seul pour chaque combinaison d'adresse IP et de port.
TLS (HTTPS) utilise par défaut le port spécifique `443`. Donc c'est ce port dont nous avons besoin.
Comme un seul process peut écouter sur ce port, le process qui le ferait serait le **Proxy de terminaison TLS**.
Le Proxy de terminaison TLS aurait accès à un ou plusieurs **certificats TLS** (certificats HTTPS).
En utilisant l'**extension SNI** mentionnée ci-dessus, le Proxy de terminaison TLS vérifierait lequel des certificats TLS (HTTPS) disponibles il doit utiliser pour cette connexion, en utilisant celui qui correspond au domaine attendu par le client.
Dans ce cas, il utiliserait le certificat pour `someapp.example.com`.
<img src="/img/deployment/https/https03.drawio.svg">
Le client **fait déjà confiance** à l'entité qui a généré ce certificat TLS (dans ce cas Let's Encrypt, mais nous verrons cela plus tard), il peut donc **vérifier** que le certificat est valide.
Ensuite, en utilisant le certificat, le client et le Proxy de terminaison TLS **décident comment chiffrer** le reste de la **communication TCP**. Cela complète la partie **Handshake TLS**.
Après cela, le client et le serveur ont une **connexion TCP chiffrée**, c'est ce que fournit TLS. Et ensuite ils peuvent utiliser cette connexion pour démarrer la véritable **communication HTTP**.
Et c'est ce qu'est **HTTPS**, c'est simplement du **HTTP** à l'intérieur d'une **connexion TLS sécurisée** au lieu d'une connexion TCP pure (non chiffrée).
/// tip | Astuce
Notez que le chiffrement de la communication se produit au **niveau TCP**, pas au niveau HTTP.
///
### Requête HTTPS { #https-request }
Maintenant que le client et le serveur (spécifiquement le navigateur et le Proxy de terminaison TLS) ont une **connexion TCP chiffrée**, ils peuvent démarrer la **communication HTTP**.
Ainsi, le client envoie une **requête HTTPS**. Il s'agit simplement d'une requête HTTP via une connexion TLS chiffrée.
<img src="/img/deployment/https/https04.drawio.svg">
### Déchiffrer la requête { #decrypt-the-request }
Le Proxy de terminaison TLS utiliserait le chiffrement convenu pour **déchiffrer la requête**, et transmettrait la **requête HTTP en clair (déchiffrée)** au process exécutant l'application (par exemple un process avec Uvicorn exécutant l'application FastAPI).
<img src="/img/deployment/https/https05.drawio.svg">
### Réponse HTTP { #http-response }
L'application traiterait la requête et enverrait une **réponse HTTP en clair (non chiffrée)** au Proxy de terminaison TLS.
<img src="/img/deployment/https/https06.drawio.svg">
### Réponse HTTPS { #https-response }
Le Proxy de terminaison TLS **chiffrerait ensuite la réponse** en utilisant la cryptographie convenue auparavant (qui a commencé avec le certificat pour `someapp.example.com`), et la renverrait au navigateur.
Ensuite, le navigateur vérifierait que la réponse est valide et chiffrée avec la bonne clé cryptographique, etc. Il **déchiffrerait ensuite la réponse** et la traiterait.
<img src="/img/deployment/https/https07.drawio.svg">
Le client (navigateur) saura que la réponse provient du bon serveur parce qu'il utilise la cryptographie qu'ils ont convenue en utilisant le **certificat HTTPS** auparavant.
### Applications multiples { #multiple-applications }
Sur le même serveur (ou les mêmes serveurs), il pourrait y avoir **plusieurs applications**, par exemple d'autres programmes d'API ou une base de données.
Un seul process peut gérer la combinaison spécifique d'IP et de port (le Proxy de terminaison TLS dans notre exemple) mais les autres applications/process peuvent également s'exécuter sur le(s) serveur(s), tant qu'ils n'essaient pas d'utiliser la même **combinaison d'IP publique et de port**.
<img src="/img/deployment/https/https08.drawio.svg">
De cette façon, le Proxy de terminaison TLS pourrait gérer HTTPS et les certificats pour **plusieurs domaines**, pour plusieurs applications, puis transmettre les requêtes à la bonne application dans chaque cas.
### Renouvellement des certificats { #certificate-renewal }
À un moment donné dans le futur, chaque certificat **expirerait** (environ 3 mois après son acquisition).
Et ensuite, il y aurait un autre programme (dans certains cas c'est un autre programme, dans certains cas cela pourrait être le même Proxy de terminaison TLS) qui parlerait à Let's Encrypt, et renouvellerait le(s) certificat(s).
<img src="/img/deployment/https/https.drawio.svg">
Les **certificats TLS** sont **associés à un nom de domaine**, pas à une adresse IP.
Ainsi, pour renouveler les certificats, le programme de renouvellement doit **prouver** à l'autorité (Let's Encrypt) qu'il **« possède » et contrôle effectivement ce domaine**.
Pour ce faire, et pour répondre à différents besoins applicatifs, il existe plusieurs façons de procéder. Voici quelques méthodes populaires :
* **Modifier certains enregistrements DNS**.
* Pour cela, le programme de renouvellement doit prendre en charge les API du fournisseur DNS, donc, selon le fournisseur DNS que vous utilisez, cela peut ou non être une option.
* **S'exécuter comme un serveur** (au moins pendant le processus d'acquisition du certificat) sur l'adresse IP publique associée au domaine.
* Comme nous l'avons dit ci-dessus, un seul process peut écouter sur une IP et un port spécifiques.
* C'est l'une des raisons pour lesquelles il est très utile que le même Proxy de terminaison TLS s'occupe aussi du processus de renouvellement des certificats.
* Sinon, vous devrez peut-être arrêter momentanément le Proxy de terminaison TLS, démarrer le programme de renouvellement pour acquérir les certificats, puis les configurer avec le Proxy de terminaison TLS, et ensuite redémarrer le Proxy de terminaison TLS. Ce n'est pas idéal, car votre/vos app(s) ne seront pas disponibles pendant le temps où le Proxy de terminaison TLS est arrêté.
Tout ce processus de renouvellement, tout en continuant de servir l'app, est l'une des principales raisons pour lesquelles vous voulez avoir un **système séparé pour gérer HTTPS** avec un Proxy de terminaison TLS au lieu de simplement utiliser les certificats TLS directement avec le serveur d'application (par ex. Uvicorn).
## En-têtes de proxy transférés { #proxy-forwarded-headers }
Lorsque vous utilisez un proxy pour gérer HTTPS, votre **serveur d'application** (par exemple Uvicorn via la FastAPI CLI) ne sait rien du processus HTTPS, il communique en HTTP en clair avec le **Proxy de terminaison TLS**.
Ce **proxy** définirait normalement certains en-têtes HTTP à la volée avant de transmettre la requête au **serveur d'application**, pour indiquer au serveur d'application que la requête est **transférée** par le proxy.
/// note | Détails techniques
Les en-têtes du proxy sont :
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
///
Néanmoins, comme le **serveur d'application** ne sait pas qu'il est derrière un **proxy** de confiance, par défaut, il ne ferait pas confiance à ces en-têtes.
Mais vous pouvez configurer le **serveur d'application** pour faire confiance aux en-têtes *forwarded* envoyés par le **proxy**. Si vous utilisez la FastAPI CLI, vous pouvez utiliser l'*option CLI* `--forwarded-allow-ips` pour lui indiquer depuis quelles IP il doit faire confiance à ces en-têtes *forwarded*.
Par exemple, si le **serveur d'application** ne reçoit la communication que du **proxy** de confiance, vous pouvez le définir sur `--forwarded-allow-ips="*"` pour lui faire confiance pour toutes les IP entrantes, car il ne recevra des requêtes que depuis l'adresse IP utilisée par le **proxy**.
De cette façon, l'application serait capable de connaître sa propre URL publique, si elle utilise HTTPS, le domaine, etc.
Ce serait utile par exemple pour gérer correctement les redirections.
/// tip | Astuce
Vous pouvez en apprendre davantage à ce sujet dans la documentation pour [Derrière un proxy - Activer les en-têtes Proxy Forwarded](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank}
///
## Récapitulatif { #recap }
Avoir **HTTPS** est très important, et assez **critique** dans la plupart des cas. La plupart des efforts que vous, en tant que développeur, devez fournir autour de HTTPS consistent simplement à **comprendre ces concepts** et comment ils fonctionnent.
Mais une fois que vous connaissez les informations de base sur **HTTPS pour les développeurs** vous pouvez facilement combiner et configurer différents outils pour vous aider à tout gérer de manière simple.
Dans certains des prochains chapitres, je vais vous montrer plusieurs exemples concrets de configuration de **HTTPS** pour des applications **FastAPI**. 🔒

View File

@@ -1,28 +1,23 @@
# Déploiement
# Déploiement { #deployment }
Le déploiement d'une application **FastAPI** est relativement simple.
## Que signifie le déploiement
## Que signifie le déploiement { #what-does-deployment-mean }
**Déployer** une application signifie effectuer les étapes nécessaires pour la rendre **disponible pour les
utilisateurs**.
**Déployer** une application signifie effectuer les étapes nécessaires pour la rendre **disponible pour les utilisateurs**.
Pour une **API Web**, cela implique normalement de la placer sur une **machine distante**, avec un **programme serveur**
qui offre de bonnes performances, une bonne stabilité, _etc._, afin que vos **utilisateurs** puissent **accéder** à
l'application efficacement et sans interruption ni problème.
Pour une **API Web**, cela implique normalement de la placer sur une **machine distante**, avec un **programme serveur** qui offre de bonnes performances, une bonne stabilité, etc, afin que vos **utilisateurs** puissent **accéder** à l'application efficacement et sans interruption ni problème.
Ceci contraste avec les étapes de **développement**, où vous êtes constamment en train de modifier le code, de le casser
et de le réparer, d'arrêter et de redémarrer le serveur de développement, _etc._
Ceci contraste avec les étapes de **développement**, où vous êtes constamment en train de modifier le code, de le casser et de le réparer, d'arrêter et de redémarrer le serveur de développement, etc.
## Stratégies de déploiement
## Stratégies de déploiement { #deployment-strategies }
Il existe plusieurs façons de procéder, en fonction de votre cas d'utilisation spécifique et des outils que vous
utilisez.
Il existe plusieurs façons de procéder, en fonction de votre cas d'utilisation spécifique et des outils que vous utilisez.
Vous pouvez **déployer un serveur** vous-même en utilisant une combinaison d'outils, vous pouvez utiliser un **service
cloud** qui fait une partie du travail pour vous, ou encore d'autres options possibles.
Vous pouvez **déployer un serveur** vous-même en utilisant une combinaison d'outils, vous pouvez utiliser un **service cloud** qui fait une partie du travail pour vous, ou encore d'autres options possibles.
Je vais vous montrer certains des principaux concepts que vous devriez probablement avoir à l'esprit lors du déploiement
d'une application **FastAPI** (bien que la plupart de ces concepts s'appliquent à tout autre type d'application web).
Par exemple, nous, l'équipe derrière FastAPI, avons créé <a href="https://fastapicloud.com" class="external-link" target="_blank">**FastAPI Cloud**</a>, afin de rendre le déploiement d'apps FastAPI sur le cloud aussi rationalisé que possible, avec la même expérience développeur que celle du travail avec FastAPI.
Je vais vous montrer certains des principaux concepts que vous devriez probablement avoir à l'esprit lors du déploiement d'une application **FastAPI** (bien que la plupart de ces concepts s'appliquent à tout autre type d'application web).
Vous verrez plus de détails à avoir en tête et certaines des techniques pour le faire dans les sections suivantes. ✨

View File

@@ -1,85 +1,78 @@
# À propos des versions de FastAPI
# À propos des versions de FastAPI { #about-fastapi-versions }
**FastAPI** est déjà utilisé en production dans de nombreuses applications et systèmes. Et la couverture de test est maintenue à 100 %. Mais son développement est toujours aussi rapide.
**FastAPI** est déjà utilisé en production dans de nombreuses applications et systèmes. Et la couverture de test est maintenue à 100 %. Mais son développement avance toujours rapidement.
De nouvelles fonctionnalités sont ajoutées fréquemment, des bogues sont corrigés régulièrement et le code est
amélioré continuellement.
De nouvelles fonctionnalités sont ajoutées fréquemment, des bogues sont corrigés régulièrement, et le code continue de saméliorer en continu.
C'est pourquoi les versions actuelles sont toujours `0.x.x`, cela reflète que chaque version peut potentiellement
recevoir des changements non rétrocompatibles. Cela suit les conventions de <a href="https://semver.org/" class="external-link"
target="_blank">versionnage sémantique</a>.
Cest pourquoi les versions actuelles sont toujours `0.x.x`, cela reflète que chaque version peut potentiellement introduire des changements incompatibles. Cela suit les conventions du <a href="https://semver.org/" class="external-link" target="_blank">Semantic Versioning</a>.
Vous pouvez créer des applications de production avec **FastAPI** dès maintenant (et vous le faites probablement depuis un certain temps), vous devez juste vous assurer que vous utilisez une version qui fonctionne correctement avec le reste de votre code.
Vous pouvez créer des applications de production avec **FastAPI** dès maintenant (et vous le faites probablement depuis un certain temps), vous devez simplement vous assurer que vous utilisez une version qui fonctionne correctement avec le reste de votre code.
## Épinglez votre version de `fastapi`
## Épinglez votre version de `fastapi` { #pin-your-fastapi-version }
Tout d'abord il faut "épingler" la version de **FastAPI** que vous utilisez à la dernière version dont vous savez
qu'elle fonctionne correctement pour votre application.
La première chose que vous devez faire est d« épingler » la version de **FastAPI** que vous utilisez à la version spécifique la plus récente dont vous savez quelle fonctionne correctement pour votre application.
Par exemple, disons que vous utilisez la version `0.45.0` dans votre application.
Par exemple, disons que vous utilisez la version `0.112.0` dans votre app.
Si vous utilisez un fichier `requirements.txt`, vous pouvez spécifier la version avec :
Si vous utilisez un fichier `requirements.txt`, vous pouvez spécifier la version avec :
```txt
fastapi==0.45.0
fastapi[standard]==0.112.0
```
ce qui signifierait que vous utiliseriez exactement la version `0.45.0`.
cela signifierait que vous utiliseriez exactement la version `0.112.0`.
Ou vous pourriez aussi l'épingler avec :
Ou vous pourriez aussi lépingler avec :
```txt
fastapi[standard]>=0.112.0,<0.113.0
```
cela signifierait que vous utiliseriez les versions `0.112.0` ou supérieures, mais inférieures à `0.113.0`, par exemple, une version `0.112.2` serait toujours acceptée.
Si vous utilisez un autre outil pour gérer vos installations, comme `uv`, Poetry, Pipenv, ou autres, ils ont tous un moyen que vous pouvez utiliser pour définir des versions spécifiques pour vos paquets.
## Versions disponibles { #available-versions }
Vous pouvez consulter les versions disponibles (p. ex. pour vérifier quelle est la dernière version actuelle) dans les [Notes de version](../release-notes.md){.internal-link target=_blank}.
## À propos des versions { #about-versions }
En suivant les conventions du Semantic Versioning, toute version inférieure à `1.0.0` peut potentiellement ajouter des changements incompatibles.
FastAPI suit également la convention selon laquelle tout changement de version « PATCH » concerne des corrections de bogues et des changements compatibles.
/// tip | Astuce
Le « PATCH » est le dernier chiffre, par exemple, dans `0.2.3`, la version PATCH est `3`.
///
Ainsi, vous devriez pouvoir épingler une version comme suit :
```txt
fastapi>=0.45.0,<0.46.0
```
cela signifierait que vous utiliseriez les versions `0.45.0` ou supérieures, mais inférieures à `0.46.0`, par exemple, une version `0.45.2` serait toujours acceptée.
Si vous utilisez un autre outil pour gérer vos installations, comme Poetry, Pipenv, ou autres, ils ont tous un moyen que vous pouvez utiliser pour définir des versions spécifiques pour vos paquets.
## Versions disponibles
Vous pouvez consulter les versions disponibles (par exemple, pour vérifier quelle est la dernière version en date) dans les [Notes de version](../release-notes.md){.internal-link target=_blank}.
## À propos des versions
Suivant les conventions de versionnage sémantique, toute version inférieure à `1.0.0` peut potentiellement ajouter
des changements non rétrocompatibles.
FastAPI suit également la convention que tout changement de version "PATCH" est pour des corrections de bogues et
des changements rétrocompatibles.
Les changements incompatibles et les nouvelles fonctionnalités sont ajoutés dans les versions « MINOR ».
/// tip | Astuce
Le "PATCH" est le dernier chiffre, par exemple, dans `0.2.3`, la version PATCH est `3`.
Le « MINOR » est le numéro au milieu, par exemple, dans `0.2.3`, la version MINOR est `2`.
///
Donc, vous devriez être capable d'épingler une version comme suit :
## Mettre à niveau les versions de FastAPI { #upgrading-the-fastapi-versions }
```txt
fastapi>=0.45.0,<0.46.0
```
Vous devez ajouter des tests pour votre app.
Les changements non rétrocompatibles et les nouvelles fonctionnalités sont ajoutés dans les versions "MINOR".
Avec **FastAPI**, cest très facile (grâce à Starlette), consultez la documentation : [Testing](../tutorial/testing.md){.internal-link target=_blank}
/// tip | Astuce
Après avoir des tests, vous pouvez alors mettre à niveau la version de **FastAPI** vers une version plus récente, et vous assurer que tout votre code fonctionne correctement en exécutant vos tests.
Le "MINOR" est le numéro au milieu, par exemple, dans `0.2.3`, la version MINOR est `2`.
Si tout fonctionne, ou après avoir effectué les changements nécessaires, et que tous vos tests passent, vous pouvez alors épingler votre `fastapi` à cette nouvelle version récente.
///
## Mise à jour des versions FastAPI
Vous devriez tester votre application.
Avec **FastAPI** c'est très facile (merci à Starlette), consultez la documentation : [Testing](../tutorial/testing.md){.internal-link target=_blank}
Après avoir effectué des tests, vous pouvez mettre à jour la version **FastAPI** vers une version plus récente, et vous assurer que tout votre code fonctionne correctement en exécutant vos tests.
Si tout fonctionne, ou après avoir fait les changements nécessaires, et que tous vos tests passent, vous pouvez
épingler votre version de `fastapi` à cette nouvelle version récente.
## À propos de Starlette
## À propos de Starlette { #about-starlette }
Vous ne devriez pas épingler la version de `starlette`.
@@ -87,15 +80,14 @@ Différentes versions de **FastAPI** utiliseront une version spécifique plus r
Ainsi, vous pouvez simplement laisser **FastAPI** utiliser la bonne version de Starlette.
## À propos de Pydantic
## À propos de Pydantic { #about-pydantic }
Pydantic inclut des tests pour **FastAPI** avec ses propres tests, ainsi les nouvelles versions de Pydantic (au-dessus
de `1.0.0`) sont toujours compatibles avec **FastAPI**.
Pydantic inclut les tests pour **FastAPI** avec ses propres tests, ainsi les nouvelles versions de Pydantic (au-dessus de `1.0.0`) sont toujours compatibles avec FastAPI.
Vous pouvez épingler Pydantic à toute version supérieure à `1.0.0` qui fonctionne pour vous et inférieure à `2.0.0`.
Vous pouvez épingler Pydantic à toute version supérieure à `1.0.0` qui fonctionne pour vous.
Par exemple :
Par exemple :
```txt
pydantic>=1.2.0,<2.0.0
pydantic>=2.7.0,<3.0.0
```

View File

@@ -1,11 +1,11 @@
# FastAPI
# FastAPI { #fastapi }
<style>
.md-content .md-typeset h1 { display: none; }
</style>
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
<a href="https://fastapi.tiangolo.com/fr"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>Framework FastAPI, haute performance, facile à apprendre, rapide à coder, prêt pour la production</em>
@@ -27,7 +27,7 @@
---
**Documentation** : <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
**Documentation** : <a href="https://fastapi.tiangolo.com/fr" target="_blank">https://fastapi.tiangolo.com</a>
**Code Source** : <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
@@ -41,120 +41,124 @@ Les principales fonctionnalités sont :
* **Rapide à coder** : Augmente la vitesse de développement des fonctionnalités d'environ 200 % à 300 %. *
* **Moins de bugs** : Réduit d'environ 40 % les erreurs induites par le développeur. *
* **Intuitif** : Excellente compatibilité avec les IDE. <abbr title="également connu sous le nom d'auto-complétion, autocomplétion, IntelliSense">Complétion</abbr> complète. Moins de temps passé à déboguer.
* **Facile** : Conçu pour être facile à utiliser et à apprendre. Moins de temps passé à lire la documentation.
* **Facile** : Conçu pour être facile à utiliser et à apprendre. Moins de temps passé à lire les documents.
* **Concis** : Diminue la duplication de code. De nombreuses fonctionnalités liées à la déclaration de chaque paramètre. Moins de bugs.
* **Robuste** : Obtenez un code prêt pour la production. Avec une documentation interactive automatique.
* **Basé sur des normes** : Basé sur (et entièrement compatible avec) les standards ouverts pour les APIs : <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (précédemment connu sous le nom de Swagger) et <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
<small>* estimation basée sur des tests d'une équipe de développement interne, construisant des applications de production.</small>
## Sponsors
## Sponsors { #sponsors }
<!-- sponsors -->
{% if sponsors %}
### Sponsor Keystone { #keystone-sponsor }
{% for sponsor in sponsors.keystone -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor -%}
### Sponsors Gold et Silver { #gold-and-silver-sponsors }
{% for sponsor in sponsors.gold -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor %}
{% endif %}
<!-- /sponsors -->
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Other sponsors</a>
<a href="https://fastapi.tiangolo.com/fr/fastapi-people/#sponsors" class="external-link" target="_blank">Autres sponsors</a>
## Opinions
## Opinions { #opinions }
"_[...] J'utilise beaucoup **FastAPI** ces derniers temps. [...] Je prévois de l'utiliser dans mon équipe pour tous les **services de ML chez Microsoft**. Certains d'entre eux seront intégrés dans le coeur de **Windows** et dans certains produits **Office**._"
"_[...] 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._"
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
---
"_Nous avons adopté la bibliothèque **FastAPI** pour créer un serveur **REST** qui peut être interrogé pour obtenir des **prédictions**. [pour Ludwig]_"
"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_"
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin et Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
---
"_**Netflix** a le plaisir d'annoncer la sortie en open-source de notre framework d'orchestration de **gestion de crise** : **Dispatch** ! [construit avec **FastAPI**]_"
"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_"
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
---
"_Je suis très enthousiaste à propos de **FastAPI**. C'est un bonheur !_"
"_Im over the moon excited about **FastAPI**. Its so fun!_"
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong>Auteur du podcast <a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a></strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
---
"_Honnêtement, ce que vous avez construit a l'air super solide et élégant. A bien des égards, c'est comme ça que je voulais que **Hug** soit - c'est vraiment inspirant de voir quelqu'un construire ça._"
"_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._"
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong> Créateur de <a href="https://github.com/hugapi/hug" target="_blank">Hug</a></strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
"_Si vous cherchez à apprendre un **framework moderne** pour créer des APIs REST, regardez **FastAPI** [...] C'est rapide, facile à utiliser et à apprendre [...]_"
"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_"
"_Nous sommes passés à **FastAPI** pour nos **APIs** [...] Je pense que vous l'aimerez [...]_"
"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_"
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong>Fondateurs de <a href="https://explosion.ai" target="_blank">Explosion AI</a> - Créateurs de <a href="https://spacy.io" target="_blank">spaCy</a></strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
---
"_Si quelqu'un cherche à construire une API Python de production, je recommande vivement **FastAPI**. Il est **bien conçu**, **simple à utiliser** et **très évolutif**. Il est devenu un **composant clé** dans notre stratégie de développement API first et il est à l'origine de nombreux automatismes et services tels que notre ingénieur virtuel TAC._"
"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._"
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
---
## **Typer**, le FastAPI des <abbr title="Command Line Interface">CLI</abbr>
## Mini-documentaire FastAPI { #fastapi-mini-documentary }
Il existe un <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">mini-documentaire FastAPI</a> sorti à la fin de 2025, vous pouvez le regarder en ligne :
<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a>
## **Typer**, le FastAPI des <abbr title="Command Line Interface">CLI</abbr> { #typer-the-fastapi-of-clis }
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
Si vous souhaitez construire une application <abbr title="Command Line Interface">CLI</abbr> utilisable dans un terminal au lieu d'une API web, regardez <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
Si vous construisez une application <abbr title="Command Line Interface">CLI</abbr> utilisable dans un terminal au lieu d'une API web, regardez <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** est le petit frère de FastAPI. Et il est destiné à être le **FastAPI des <abbr title="Command Line Interface">CLI</abbr>**. ⌨️ 🚀
## Prérequis
## Prérequis { #requirements }
FastAPI repose sur les épaules de géants :
* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> pour les parties web.
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> pour les parties données.
## Installation
## Installation { #installation }
Créez et activez un <a href="https://fastapi.tiangolo.com/fr/virtual-environments/" class="external-link" target="_blank">environnement virtuel</a> puis installez FastAPI :
<div class="termy">
```console
$ pip install fastapi
$ pip install "fastapi[standard]"
---> 100%
```
</div>
Vous aurez également besoin d'un serveur ASGI pour la production tel que <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> ou <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
**Remarque** : vous devez mettre `"fastapi[standard]"` entre guillemets pour vous assurer que cela fonctionne dans tous les terminaux.
<div class="termy">
## Exemple { #example }
```console
$ pip install "uvicorn[standard]"
### Créez-le { #create-it }
---> 100%
```
</div>
## Exemple
### Créez
* Créez un fichier `main.py` avec :
Créez un fichier `main.py` avec :
```Python
from typing import Union
@@ -175,7 +179,7 @@ def read_item(item_id: int, q: Union[str, None] = None):
```
<details markdown="1">
<summary>Ou utilisez <code>async def</code> ...</summary>
<summary>Ou utilisez <code>async def</code>...</summary>
Si votre code utilise `async` / `await`, utilisez `async def` :
@@ -197,24 +201,37 @@ async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
**Note**
**Remarque** :
Si vous n'êtes pas familier avec cette notion, consultez la section _"Vous êtes pressés ?"_ à propos de <a href="https://fastapi.tiangolo.com/fr/async/#vous-etes-presses" target="_blank">`async` et `await` dans la documentation</a>.
Si vous ne savez pas, consultez la section _« In a hurry? »_ à propos de <a href="https://fastapi.tiangolo.com/fr/async/#in-a-hurry" target="_blank">`async` et `await` dans les documents</a>.
</details>
### Lancez
### Lancez-le { #run-it }
Lancez le serveur avec :
<div class="termy">
```console
$ uvicorn main:app --reload
$ fastapi dev main.py
╭────────── 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 [28720]
INFO: Started server process [28722]
INFO: Started reloader process [2248755] using WatchFiles
INFO: Started server process [2248757]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
@@ -222,21 +239,21 @@ INFO: Application startup complete.
</div>
<details markdown="1">
<summary>À propos de la commande <code>uvicorn main:app --reload</code> ...</summary>
<summary>À propos de la commande <code>fastapi dev main.py</code>...</summary>
La commande `uvicorn main:app` fait référence à :
La commande `fastapi dev` lit votre fichier `main.py`, détecte l'app **FastAPI** qu'il contient et démarre un serveur en utilisant <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>.
* `main` : le fichier `main.py` (le "module" Python).
* `app` : l'objet créé à l'intérieur de `main.py` avec la ligne `app = FastAPI()`.
* `--reload` : fait redémarrer le serveur après des changements de code. À n'utiliser que pour le développement.
Par défaut, `fastapi dev` démarre avec l'auto-reload activé pour le développement local.
Vous pouvez en savoir plus dans <a href="https://fastapi.tiangolo.com/fr/fastapi-cli/" target="_blank">la documentation de FastAPI CLI</a>.
</details>
### Vérifiez
### Vérifiez-le { #check-it }
Ouvrez votre navigateur à l'adresse <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
Vous obtenez alors cette réponse <abbr title="JavaScript Object Notation">JSON</abbr> :
Vous verrez la réponse JSON comme suit :
```JSON
{"item_id": 5, "q": "somequery"}
@@ -246,10 +263,10 @@ Vous venez de créer une API qui :
* Reçoit les requêtes HTTP pour les _chemins_ `/` et `/items/{item_id}`.
* Les deux _chemins_ acceptent des <em>opérations</em> `GET` (également connu sous le nom de _méthodes_ HTTP).
* Le _chemin_ `/items/{item_id}` a un _<abbr title="en anglais : path parameter">paramètre</abbr>_ `item_id` qui doit être un `int`.
* Le _chemin_ `/items/{item_id}` a un _<abbr title="en anglais : query param">paramètre de requête</abbr>_ optionnel `q` de type `str`.
* Le _chemin_ `/items/{item_id}` a un paramètre de chemin `item_id` qui doit être un `int`.
* Le _chemin_ `/items/{item_id}` a un paramètre de requête optionnel `q` de type `str`.
### Documentation API interactive
### Documentation API interactive { #interactive-api-docs }
Maintenant, rendez-vous sur <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
@@ -257,17 +274,17 @@ Vous verrez la documentation interactive automatique de l'API (fournie par <a hr
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Documentation API alternative
### Documentation API alternative { #alternative-api-docs }
Et maintenant, rendez-vous sur <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Vous verrez la documentation interactive automatique de l'API (fournie par <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>) :
Vous verrez la documentation automatique alternative (fournie par <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>) :
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Exemple plus poussé
## Mettre à niveau l'exemple { #example-upgrade }
Maintenant, modifiez le fichier `main.py` pour recevoir <abbr title="en anglais : body">le corps</abbr> d'une requête `PUT`.
Maintenant, modifiez le fichier `main.py` pour recevoir un corps à partir d'une requête `PUT`.
Déclarez ce corps en utilisant les types Python standards, grâce à Pydantic.
@@ -301,35 +318,35 @@ def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
Le serveur se recharge normalement automatiquement (car vous avez pensé à `--reload` dans la commande `uvicorn` ci-dessus).
Le serveur `fastapi dev` devrait se recharger automatiquement.
### Plus loin avec la documentation API interactive
### Mettre à niveau la documentation API interactive { #interactive-api-docs-upgrade }
Maintenant, rendez-vous sur <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
* La documentation interactive de l'API sera automatiquement mise à jour, y compris le nouveau corps de la requête :
* La documentation interactive de l'API sera automatiquement mise à jour, y compris le nouveau corps :
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* Cliquez sur le bouton "Try it out", il vous permet de renseigner les paramètres et d'interagir directement avec l'API :
* Cliquez sur le bouton « Try it out », il vous permet de renseigner les paramètres et d'interagir directement avec l'API :
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png)
* Cliquez ensuite sur le bouton "Execute", l'interface utilisateur communiquera avec votre API, enverra les paramètres, obtiendra les résultats et les affichera à l'écran :
* Cliquez ensuite sur le bouton « Execute », l'interface utilisateur communiquera avec votre API, enverra les paramètres, obtiendra les résultats et les affichera à l'écran :
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png)
### Plus loin avec la documentation API alternative
### Mettre à niveau la documentation API alternative { #alternative-api-docs-upgrade }
Et maintenant, rendez-vous sur <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
* La documentation alternative reflétera également le nouveau paramètre de requête et le nouveau corps :
* La documentation alternative reflétera également le nouveau paramètre de requête et le corps :
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### En résumé
### En résumé { #recap }
En résumé, vous déclarez **une fois** les types de paramètres, <abbr title="en anglais : body">le corps</abbr> de la requête, etc. en tant que paramètres de fonction.
En résumé, vous déclarez **une fois** les types de paramètres, le corps, etc. en tant que paramètres de fonction.
Vous faites cela avec les types Python standard modernes.
@@ -351,48 +368,48 @@ item: Item
... et avec cette déclaration unique, vous obtenez :
* Une assistance dans votre IDE, notamment :
* Une assistance dans votre éditeur, notamment :
* la complétion.
* la vérification des types.
* La validation des données :
* des erreurs automatiques et claires lorsque les données ne sont pas valides.
* une validation même pour les objets <abbr title="JavaScript Object Notation">JSON</abbr> profondément imbriqués.
* <abbr title="aussi connu sous le nom de : serialization, parsing, marshalling">Une conversion</abbr> des données d'entrée : venant du réseau et allant vers les données et types de Python, permettant de lire :
* le <abbr title="JavaScript Object Notation">JSON</abbr>.
* <abbr title="en anglais : path parameters">les paramètres du chemin</abbr>.
* <abbr title="en anglais : query parameters">les paramètres de la requête</abbr>.
* les cookies.
* <abbr title="en anglais : headers">les en-têtes</abbr>.
* <abbr title="en anglais : forms">les formulaires</abbr>.
* <abbr title="en anglais : files">les fichiers</abbr>.
* <abbr title="aussi connu sous le nom de : serialization, parsing, marshalling">La conversion</abbr> des données de sortie : conversion des données et types Python en données réseau (au format <abbr title="JavaScript Object Notation">JSON</abbr>), permettant de convertir :
* les types Python (`str`, `int`, `float`, `bool`, `list`, etc).
* une validation même pour les objets JSON profondément imbriqués.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> des données d'entrée : venant du réseau et allant vers les données et types de Python. Lecture de :
* JSON.
* Les paramètres de chemin.
* Les paramètres de requête.
* Les cookies.
* Les en-têtes.
* Les formulaires.
* Les fichiers.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> des données de sortie : conversion des données et types Python en données réseau (au format JSON) :
* Convertir les types Python (`str`, `int`, `float`, `bool`, `list`, etc).
* les objets `datetime`.
* les objets `UUID`.
* les modèles de base de données.
* ... et beaucoup plus.
* La documentation API interactive automatique, avec 2 interfaces utilisateur au choix :
* La documentation API interactive automatique, incluant 2 interfaces utilisateur alternatives :
* Swagger UI.
* ReDoc.
---
Pour revenir à l'exemple de code précédent, **FastAPI** permet de :
Pour revenir à l'exemple de code précédent, **FastAPI** va :
* Valider que `item_id` existe dans le chemin des requêtes `GET` et `PUT`.
* Valider que `item_id` est de type `int` pour les requêtes `GET` et `PUT`.
* Si ce n'est pas le cas, le client voit une erreur utile et claire.
* Vérifier qu'il existe un paramètre de requête facultatif nommé `q` (comme dans `http://127.0.0.1:8000/items/foo?q=somequery`) pour les requêtes `GET`.
* Puisque le paramètre `q` est déclaré avec `= None`, il est facultatif.
* Sans le `None`, il serait nécessaire (comme l'est <abbr title="en anglais : body">le corps</abbr> de la requête dans le cas du `PUT`).
* Pour les requêtes `PUT` vers `/items/{item_id}`, de lire <abbr title="en anglais : body">le corps</abbr> en <abbr title="JavaScript Object Notation">JSON</abbr> :
* Si ce n'est pas le cas, le client verra une erreur utile et claire.
* Vérifier qu'il existe un paramètre de requête optionnel nommé `q` (comme dans `http://127.0.0.1:8000/items/foo?q=somequery`) pour les requêtes `GET`.
* Puisque le paramètre `q` est déclaré avec `= None`, il est optionnel.
* Sans le `None`, il serait nécessaire (comme l'est le corps dans le cas du `PUT`).
* Pour les requêtes `PUT` vers `/items/{item_id}`, lire le corps en JSON :
* Vérifier qu'il a un attribut obligatoire `name` qui devrait être un `str`.
* Vérifier qu'il a un attribut obligatoire `prix` qui doit être un `float`.
* Vérifier qu'il a un attribut obligatoire `price` qui doit être un `float`.
* Vérifier qu'il a un attribut facultatif `is_offer`, qui devrait être un `bool`, s'il est présent.
* Tout cela fonctionnerait également pour les objets <abbr title="JavaScript Object Notation">JSON</abbr> profondément imbriqués.
* Convertir de et vers <abbr title="JavaScript Object Notation">JSON</abbr> automatiquement.
* Tout cela fonctionnerait également pour les objets JSON profondément imbriqués.
* Convertir automatiquement depuis et vers JSON.
* Documenter tout avec OpenAPI, qui peut être utilisé par :
* Les systèmes de documentation interactifs.
* Les systèmes de documentation interactive.
* Les systèmes de génération automatique de code client, pour de nombreuses langues.
* Fournir directement 2 interfaces web de documentation interactive.
@@ -420,53 +437,129 @@ Essayez de changer la ligne contenant :
... et voyez comment votre éditeur complétera automatiquement les attributs et connaîtra leurs types :
![compatibilité IDE](https://fastapi.tiangolo.com/img/vscode-completion.png)
![compatibilité éditeur](https://fastapi.tiangolo.com/img/vscode-completion.png)
Pour un exemple plus complet comprenant plus de fonctionnalités, voir le <a href="https://fastapi.tiangolo.com/fr/tutorial/">Tutoriel - Guide utilisateur</a>.
**Spoiler alert** : le tutoriel - guide utilisateur inclut :
* Déclaration de **paramètres** provenant d'autres endroits différents comme : **<abbr title="en anglais : headers">en-têtes</abbr>.**, **cookies**, **champs de formulaire** et **fichiers**.
* L'utilisation de **contraintes de validation** comme `maximum_length` ou `regex`.
* Un **<abbr title="aussi connu sous le nom de composants, ressources, fournisseurs, services, injectables">système d'injection de dépendance </abbr>** très puissant et facile à utiliser .
* Sécurité et authentification, y compris la prise en charge de **OAuth2** avec les **<abbr title="en anglais : JWT tokens">jetons <abbr title="JSON Web Tokens">JWT</abbr></abbr>** et l'authentification **HTTP Basic**.
* Des techniques plus avancées (mais tout aussi faciles) pour déclarer les **modèles <abbr title="JavaScript Object Notation">JSON</abbr> profondément imbriqués** (grâce à Pydantic).
* Déclaration de **paramètres** provenant d'autres endroits différents comme : **en-têtes**, **cookies**, **champs de formulaire** et **fichiers**.
* Comment définir des **contraintes de validation** comme `maximum_length` ou `regex`.
* Un **système <abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** très puissant et facile à utiliser.
* Sécurité et authentification, y compris la prise en charge de **OAuth2** avec des **JWT tokens** et l'authentification **HTTP Basic**.
* Des techniques plus avancées (mais tout aussi faciles) pour déclarer des **modèles JSON profondément imbriqués** (grâce à Pydantic).
* Intégration de **GraphQL** avec <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> et d'autres bibliothèques.
* D'obtenir de nombreuses fonctionnalités supplémentaires (grâce à Starlette) comme :
* De nombreuses fonctionnalités supplémentaires (grâce à Starlette) comme :
* **WebSockets**
* de tester le code très facilement avec `requests` et `pytest`
* **<abbr title="Cross-Origin Resource Sharing">CORS</abbr>**
* des tests extrêmement simples basés sur HTTPX et `pytest`
* **CORS**
* **Cookie Sessions**
* ... et plus encore.
## Performance
### Déployer votre app (optionnel) { #deploy-your-app-optional }
Les benchmarks TechEmpower indépendants montrent que les applications **FastAPI** s'exécutant sous Uvicorn sont <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank"> parmi les frameworks existants en Python les plus rapides </a>, juste derrière Starlette et Uvicorn (utilisés en interne par FastAPI). (*)
Vous pouvez éventuellement déployer votre app FastAPI sur <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, allez vous inscrire sur la liste d'attente si ce n'est pas déjà fait. 🚀
Si vous avez déjà un compte **FastAPI Cloud** (nous vous avons invité depuis la liste d'attente 😉), vous pouvez déployer votre application avec une commande.
Avant de déployer, assurez-vous que vous êtes connecté :
<div class="termy">
```console
$ fastapi login
You are logged in to FastAPI Cloud 🚀
```
</div>
Puis déployez votre app :
<div class="termy">
```console
$ fastapi deploy
Deploying to FastAPI Cloud...
✅ Deployment successful!
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
```
</div>
C'est tout ! Vous pouvez maintenant accéder à votre app à cette URL. ✨
#### À propos de FastAPI Cloud { #about-fastapi-cloud }
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** est construit par le même auteur et la même équipe derrière **FastAPI**.
Il rationalise le processus de **construction**, de **déploiement** et d'**accès** à une API avec un minimum d'effort.
Il apporte la même **expérience développeur** de construction d'apps avec FastAPI au **déploiement** dans le cloud. 🎉
FastAPI Cloud est le sponsor principal et le fournisseur de financement des projets open source *FastAPI and friends*. ✨
#### Déployer sur d'autres fournisseurs cloud { #deploy-to-other-cloud-providers }
FastAPI est open source et basé sur des normes. Vous pouvez déployer des apps FastAPI sur n'importe quel fournisseur cloud de votre choix.
Suivez les guides de votre fournisseur cloud pour y déployer des apps FastAPI. 🤓
## Performance { #performance }
Les benchmarks TechEmpower indépendants montrent que les applications **FastAPI** s'exécutant sous Uvicorn sont <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">parmi les frameworks Python disponibles les plus rapides</a>, juste derrière Starlette et Uvicorn eux-mêmes (utilisés en interne par FastAPI). (*)
Pour en savoir plus, consultez la section <a href="https://fastapi.tiangolo.com/fr/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>.
## Dépendances facultatives
## Dépendances { #dependencies }
Utilisées par Pydantic:
FastAPI dépend de Pydantic et de Starlette.
### Dépendances `standard` { #standard-dependencies }
Quand vous installez FastAPI avec `pip install "fastapi[standard]"`, il est fourni avec le groupe `standard` de dépendances optionnelles :
Utilisées par Pydantic :
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - pour la validation des adresses email.
Utilisées par Starlette :
* <a href="https://requests.readthedocs.io" target="_blank"><code>requests</code></a> - Obligatoire si vous souhaitez utiliser `TestClient`.
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Obligatoire si vous souhaitez utiliser `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Obligatoire si vous souhaitez utiliser la configuration de template par défaut.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Obligatoire si vous souhaitez supporter le <abbr title="convertit la chaine de caractère d'une requête HTTP en donnée Python">"décodage"</abbr> de formulaire avec `request.form()`.
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - Obligatoire pour la prise en charge de `SessionMiddleware`.
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Obligatoire pour le support `SchemaGenerator` de Starlette (vous n'en avez probablement pas besoin avec FastAPI).
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Obligatoire si vous souhaitez supporter le <abbr title="conversion de la chaîne qui provient d'une requête HTTP en données Python">« parsing »</abbr> de formulaire, avec `request.form()`.
Utilisées par FastAPI / Starlette :
Utilisées par FastAPI :
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - pour le serveur qui charge et sert votre application. Cela inclut `uvicorn[standard]`, qui inclut certaines dépendances (par exemple `uvloop`) nécessaires pour un service à haute performance.
* `fastapi-cli[standard]` - pour fournir la commande `fastapi`.
* Cela inclut `fastapi-cloud-cli`, qui vous permet de déployer votre application FastAPI sur <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
### Sans les dépendances `standard` { #without-standard-dependencies }
Si vous ne voulez pas inclure les dépendances optionnelles `standard`, vous pouvez installer avec `pip install fastapi` au lieu de `pip install "fastapi[standard]"`.
### Sans `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
Si vous voulez installer FastAPI avec les dépendances standard mais sans `fastapi-cloud-cli`, vous pouvez installer avec `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
### Dépendances optionnelles supplémentaires { #additional-optional-dependencies }
Il y a quelques dépendances supplémentaires que vous pourriez vouloir installer.
Dépendances optionnelles Pydantic supplémentaires :
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - pour la gestion des paramètres.
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - pour les types supplémentaires à utiliser avec Pydantic.
Dépendances optionnelles FastAPI supplémentaires :
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - Pour le serveur qui charge et sert votre application.
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Obligatoire si vous voulez utiliser `ORJSONResponse`.
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Obligatoire si vous souhaitez utiliser `UJSONResponse`.
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Obligatoire si vous voulez utiliser `UJSONResponse`.
Vous pouvez tout installer avec `pip install fastapi[all]`.
## Licence
## Licence { #license }
Ce projet est soumis aux termes de la licence MIT.

View File

@@ -1,5 +1,5 @@
# Apprendre
# Apprendre { #learn }
Voici les sections introductives et les tutoriels pour apprendre **FastAPI**.
Vous pouvez considérer ceci comme un **manuel**, un **cours**, la **méthode officielle** et recommandée pour appréhender FastAPI. 😎
Vous pouvez considérer ceci comme un **livre**, un **cours**, la **méthode officielle** et recommandée pour apprendre FastAPI. 😎

View File

@@ -1,84 +1,28 @@
# Génération de projets - Modèle
# Modèle Full Stack FastAPI { #full-stack-fastapi-template }
Vous pouvez utiliser un générateur de projet pour commencer, qui réalisera pour vous la mise en place de bases côté architecture globale, sécurité, base de données et premières routes d'API.
Les modèles, bien quils soient généralement fournis avec une configuration spécifique, sont conçus pour être flexibles et personnalisables. Cela vous permet de les modifier et de les adapter aux exigences de votre projet, ce qui en fait un excellent point de départ. 🏁
Un générateur de projet fera toujours une mise en place très subjective que vous devriez modifier et adapter suivant vos besoins, mais cela reste un bon point de départ pour vos projets.
Vous pouvez utiliser ce modèle pour commencer, car il inclut déjà une grande partie de la configuration initiale, de la sécurité, de la base de données et certains endpoints dAPI déjà faits pour vous.
## Full Stack FastAPI PostgreSQL
Dépôt GitHub : <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Modèle Full Stack FastAPI</a>
GitHub : <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a>
## Modèle Full Stack FastAPI - Stack technologique et fonctionnalités { #full-stack-fastapi-template-technology-stack-and-features }
### Full Stack FastAPI PostgreSQL - Fonctionnalités
* Intégration **Docker** complète (basée sur Docker).
* Déploiement Docker en mode <a href="https://docs.docker.com/engine/swarm/" class="external-link" target="_blank">Swarm</a>
* Intégration **Docker Compose** et optimisation pour développement local.
* Serveur web Python **prêt au déploiement** utilisant Uvicorn et Gunicorn.
* Backend Python <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**</a> :
* **Rapide** : Très hautes performances, comparables à **NodeJS** ou **Go** (grâce à Starlette et Pydantic).
* **Intuitif** : Excellent support des éditeurs. <abbr title="aussi appelée auto-complétion, autocomplétion, IntelliSense...">Complétion</abbr> partout. Moins de temps passé à déboguer.
* **Facile** : Fait pour être facile à utiliser et apprendre. Moins de temps passé à lire de la documentation.
* **Concis** : Minimise la duplication de code. Plusieurs fonctionnalités à chaque déclaration de paramètre.
* **Robuste** : Obtenez du code prêt pour être utilisé en production. Avec de la documentation automatique interactive.
* **Basé sur des normes** : Basé sur (et totalement compatible avec) les normes ouvertes pour les APIs : <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> et <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
* <a href="https://fastapi.tiangolo.com/features/" class="external-link" target="_blank">**Et bien d'autres fonctionnalités**</a> comme la validation automatique, la sérialisation, l'authentification avec OAuth2 JWT tokens, etc.
* Hashage de **mots de passe sécurisé** par défaut.
* Authentification par **jetons JWT**.
* Modèles **SQLAlchemy** (indépendants des extensions Flask, afin qu'ils puissent être utilisés directement avec des *workers* Celery).
* Modèle de démarrages basiques pour les utilisateurs (à modifier et supprimer au besoin).
* Migrations **Alembic**.
* **CORS** (partage des ressources entre origines multiples, ou *Cross Origin Resource Sharing*).
* *Worker* **Celery** pouvant importer et utiliser les modèles et le code du reste du backend.
* Tests du backend REST basés sur **Pytest**, intégrés dans Docker, pour que vous puissiez tester toutes les interactions de l'API indépendamment de la base de données. Étant exécutés dans Docker, les tests peuvent utiliser un nouvel entrepôt de données créé de zéro à chaque fois (vous pouvez donc utiliser ElasticSearch, MongoDB, CouchDB, etc. et juste tester que l'API fonctionne).
* Intégration Python facile avec **Jupyter Kernels** pour le développement à distance ou intra-Docker avec des extensions comme Atom Hydrogen ou Visual Studio Code Jupyter.
* Frontend **Vue** :
* Généré avec Vue CLI.
* Gestion de l'**Authentification JWT**.
* Page de connexion.
* Après la connexion, page de tableau de bord principal.
* Tableau de bord principal avec création et modification d'utilisateurs.
* Modification de ses propres caractéristiques utilisateur.
* **Vuex**.
* **Vue-router**.
* **Vuetify** pour de magnifiques composants *material design*.
* **TypeScript**.
* Serveur Docker basé sur **Nginx** (configuré pour être facilement manipulé avec Vue-router).
* Utilisation de *Docker multi-stage building*, pour ne pas avoir besoin de sauvegarder ou *commit* du code compilé.
* Tests frontend exécutés à la compilation (pouvant être désactivés).
* Fait aussi modulable que possible, pour pouvoir fonctionner comme tel, tout en pouvant être utilisé qu'en partie grâce à Vue CLI.
* **PGAdmin** pour les bases de données PostgreSQL, facilement modifiable pour utiliser PHPMYAdmin ou MySQL.
* **Flower** pour la surveillance de tâches Celery.
* Équilibrage de charge entre le frontend et le backend avec **Traefik**, afin de pouvoir avoir les deux sur le même domaine, séparés par chemins, mais servis par différents conteneurs.
* Intégration Traefik, comprenant la génération automatique de certificat **HTTPS** Let's Encrypt.
* GitLab **CI** (intégration continue), comprenant des tests pour le frontend et le backend.
## Full Stack FastAPI Couchbase
GitHub : <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-couchbase</a>
⚠️ **ATTENTION** ⚠️
Si vous démarrez un nouveau projet de zéro, allez voir les alternatives au début de cette page.
Par exemple, le générateur de projet <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">Full Stack FastAPI PostgreSQL</a> peut être une meilleure alternative, étant activement maintenu et utilisé et comprenant toutes les nouvelles fonctionnalités et améliorations.
Vous êtes toujours libre d'utiliser le générateur basé sur Couchbase si vous le voulez, cela devrait probablement fonctionner correctement, et si vous avez déjà un projet généré en utilisant ce dernier, cela devrait fonctionner aussi (et vous l'avez déjà probablement mis à jour suivant vos besoins).
Vous pouvez en apprendre plus dans la documentation du dépôt GithHub.
## Full Stack FastAPI MongoDB
...viendra surement plus tard, suivant le temps que j'ai. 😅 🎉
## Modèles d'apprentissage automatique avec spaCy et FastAPI
GitHub : <a href="https://github.com/microsoft/cookiecutter-spacy-fastapi" class="external-link" target="_blank">https://github.com/microsoft/cookiecutter-spacy-fastapi</a>
## Modèles d'apprentissage automatique avec spaCy et FastAPI - Fonctionnalités
* Intégration d'un modèle NER **spaCy**.
* Formatage de requête pour **Azure Cognitive Search**.
* Serveur Python web **prêt à utiliser en production** utilisant Uvicorn et Gunicorn.
* Déploiement CI/CD Kubernetes pour **Azure DevOps** (AKS).
* **Multilangues**. Choisissez facilement l'une des langues intégrées à spaCy durant la mise en place du projet.
* **Facilement généralisable** à d'autres bibliothèques similaires (Pytorch, Tensorflow), et non juste spaCy.
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/fr) pour lAPI backend Python.
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) pour les interactions avec la base de données SQL en Python (ORM).
- 🔍 [Pydantic](https://docs.pydantic.dev), utilisé par FastAPI, pour la validation des données et la gestion des paramètres.
- 💾 [PostgreSQL](https://www.postgresql.org) comme base de données SQL.
- 🚀 [React](https://react.dev) pour le frontend.
- 💃 Utilisation de TypeScript, des hooks, de Vite et dautres éléments dune stack frontend moderne.
- 🎨 [Tailwind CSS](https://tailwindcss.com) et [shadcn/ui](https://ui.shadcn.com) pour les composants du frontend.
- 🤖 Un client frontend généré automatiquement.
- 🧪 [Playwright](https://playwright.dev) pour les tests End-to-End.
- 🦇 Prise en charge du mode sombre.
- 🐋 [Docker Compose](https://www.docker.com) pour le développement et la production.
- 🔒 Hashage sécurisé des mots de passe par défaut.
- 🔑 Authentification JWT (JSON Web Token).
- 📫 Récupération du mot de passe par e-mail.
- ✅ Tests avec [Pytest](https://pytest.org).
- 📞 [Traefik](https://traefik.io) comme reverse proxy / load balancer.
- 🚢 Instructions de déploiement utilisant Docker Compose, y compris comment configurer un proxy Traefik frontend pour gérer des certificats HTTPS automatiques.
- 🏭 CI (intégration continue) et CD (déploiement continu) basées sur GitHub Actions.

View File

@@ -1,70 +1,68 @@
# Introduction aux Types Python
# Introduction aux Types Python { #python-types-intro }
Python supporte des annotations de type (ou *type hints*) optionnelles.
Python supporte des «type hints» optionnels (aussi appelés «type annotations»).
Ces annotations de type constituent une syntaxe spéciale qui permet de déclarer le <abbr title="par exemple : str, int, float, bool">type</abbr> d'une variable.
Ces **«type hints»** ou annotations sont une syntaxe spéciale qui permet de déclarer le <abbr title="par exemple : str, int, float, bool">type</abbr> d'une variable.
En déclarant les types de vos variables, cela permet aux différents outils comme les éditeurs de texte d'offrir un meilleur support.
En déclarant des types pour vos variables, les éditeurs et les outils peuvent vous offrir un meilleur support.
Ce chapitre n'est qu'un **tutoriel rapide / rappel** sur les annotations de type Python.
Seulement le minimum nécessaire pour les utiliser avec **FastAPI** sera couvert... ce qui est en réalité très peu.
Ceci n'est qu'un **tutoriel rapide / rappel** sur les «type hints» de Python. Il couvre seulement le minimum nécessaire pour les utiliser avec **FastAPI**... ce qui est en réalité très peu.
**FastAPI** est totalement basé sur ces annotations de type, qui lui donnent de nombreux avantages.
**FastAPI** est entièrement basé sur ces «type hints», ils lui donnent de nombreux avantages et bénéfices.
Mais même si vous n'utilisez pas ou n'utiliserez jamais **FastAPI**, vous pourriez bénéficier d'apprendre quelques choses sur ces dernières.
Mais même si vous n'utilisez jamais **FastAPI**, vous auriez intérêt à en apprendre un peu à leur sujet.
/// note
/// note | Remarque
Si vous êtes un expert Python, et que vous savez déjà **tout** sur les annotations de type, passez au chapitre suivant.
Si vous êtes un expert Python, et que vous savez déjà tout sur les «type hints», passez au chapitre suivant.
///
## Motivations
## Motivation { #motivation }
Prenons un exemple simple :
Commençons par un exemple simple :
{*../../docs_src/python_types/tutorial001.py*}
{* ../../docs_src/python_types/tutorial001_py39.py *}
Exécuter ce programe affiche :
L'exécution de ce programme affiche :
```
John Doe
```
La fonction :
La fonction fait ce qui suit :
* Prend un `first_name` et un `last_name`.
* Convertit la première lettre de chaque paramètre en majuscules grâce à `title()`.
* Concatène les résultats avec un espace entre les deux.
* Convertit la première lettre de chacun en majuscule avec `title()`.
* Les <abbr title="Puts them together, as one. With the contents of one after the other.">concatène</abbr> avec un espace au milieu.
{*../../docs_src/python_types/tutorial001.py hl[2] *}
{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
### Limitations
### Le modifier { #edit-it }
C'est un programme très simple.
Mais maintenant imaginez que vous l'écriviez de zéro.
Mais imaginez maintenant que vous l'écriviez de zéro.
À un certain point vous auriez commencé la définition de la fonction, vous aviez les paramètres prêts.
À un certain point, vous auriez commencé la définition de la fonction, vous aviez les paramètres prêts...
Mais vous aviez besoin de "cette méthode qui convertit la première lettre en majuscule".
Mais ensuite vous devez appeler « cette méthode qui convertit la première lettre en majuscule ».
Était-ce `upper` ? `uppercase` ? `first_uppercase` ? `capitalize` ?
Était-ce `upper` ? Était-ce `uppercase` ? `first_uppercase` ? `capitalize` ?
Vous essayez donc d'utiliser le vieil ami du programmeur, l'auto-complétion de l'éditeur.
Ensuite, vous essayez avec le vieil ami du programmeur, l'autocomplétion de l'éditeur.
Vous écrivez le premier paramètre, `first_name`, puis un point (`.`) et appuyez sur `Ctrl+Espace` pour déclencher l'auto-complétion.
Vous tapez le premier paramètre de la fonction, `first_name`, puis un point (`.`) et appuyez sur `Ctrl+Space` pour déclencher la complétion.
Mais malheureusement, rien d'utile n'en résulte :
Mais, malheureusement, vous n'obtenez rien d'utile :
<img src="/img/python-types/image01.png">
### Ajouter des types
### Ajouter des types { #add-types }
Modifions une seule ligne de la version précédente.
Nous allons changer seulement cet extrait, les paramètres de la fonction, de :
Nous allons changer exactement ce fragment, les paramètres de la fonction, de :
```Python
first_name, last_name
@@ -78,11 +76,11 @@ Nous allons changer seulement cet extrait, les paramètres de la fonction, de :
C'est tout.
Ce sont des annotations de types :
Ce sont les «type hints» :
{*../../docs_src/python_types/tutorial002.py hl[1] *}
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
À ne pas confondre avec la déclaration de valeurs par défaut comme ici :
Ce n'est pas la même chose que de déclarer des valeurs par défaut, comme ce serait le cas avec :
```Python
first_name="john", last_name="doe"
@@ -90,210 +88,377 @@ Ce sont des annotations de types :
C'est une chose différente.
On utilise un deux-points (`:`), et pas un égal (`=`).
Nous utilisons des deux-points (`:`), pas des signes égal (`=`).
Et ajouter des annotations de types ne ce normalement pas de différence avec le comportement qui aurait eu lieu si elles n'étaient pas là.
Et ajouter des «type hints» ne change normalement pas ce qui se passe par rapport à ce qui se passerait sans eux.
Maintenant, imaginez que vous êtes en train de créer cette fonction, mais avec des annotations de type cette fois.
Mais maintenant, imaginez que vous êtes à nouveau en plein milieu de la création de cette fonction, mais avec des «type hints».
Au même moment que durant l'exemple précédent, vous essayez de déclencher l'auto-complétion et vous voyez :
Au même endroit, vous essayez de déclencher l'autocomplétion avec `Ctrl+Space` et vous voyez :
<img src="/img/python-types/image02.png">
Vous pouvez donc dérouler les options jusqu'à trouver la méthode à laquelle vous pensiez.
Avec ça, vous pouvez faire défiler, voir les options, jusqu'à trouver celle qui «vous dit quelque chose» :
<img src="/img/python-types/image03.png">
## Plus de motivations
## Plus de motivation { #more-motivation }
Cette fonction possède déjà des annotations de type :
Regardez cette fonction, elle a déjà des «type hints» :
{*../../docs_src/python_types/tutorial003.py hl[1] *}
{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
Comme l'éditeur connaît le type des variables, vous n'avez pas seulement l'auto-complétion, mais aussi de la détection d'erreurs :
Parce que l'éditeur connaît les types des variables, vous n'avez pas seulement la complétion, vous avez aussi des vérifications d'erreurs :
<img src="/img/python-types/image04.png">
Maintenant que vous avez connaissance du problème, convertissez `age` en <abbr title="string">chaîne de caractères</abbr> grâce à `str(age)` :
Maintenant vous savez que vous devez le corriger, convertir `age` en chaîne de caractères avec `str(age)` :
{*../../docs_src/python_types/tutorial004.py hl[2] *}
{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
## Déclarer des types
## Déclarer des types { #declaring-types }
Vous venez de voir là où les types sont généralement déclarés : dans les paramètres de fonctions.
Vous venez de voir l'endroit principal pour déclarer des « type hints». En tant que paramètres de fonction.
C'est aussi ici que vous les utiliseriez avec **FastAPI**.
C'est aussi l'endroit principal où vous les utiliseriez avec **FastAPI**.
### Types simples
### Types simples { #simple-types }
Vous pouvez déclarer tous les types de Python, pas seulement `str`.
Vous pouvez déclarer tous les types standards de Python, pas seulement `str`.
Comme par exemple :
Vous pouvez utiliser, par exemple :
* `int`
* `float`
* `bool`
* `bytes`
{*../../docs_src/python_types/tutorial005.py hl[1] *}
{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
### Types génériques avec des paramètres de types
### Types génériques avec des paramètres de type { #generic-types-with-type-parameters }
Il existe certaines structures de données qui contiennent d'autres valeurs, comme `dict`, `list`, `set` et `tuple`. Et les valeurs internes peuvent elles aussi avoir leurs propres types.
Il existe des structures de données qui peuvent contenir d'autres valeurs, comme `dict`, `list`, `set` et `tuple`. Et les valeurs internes peuvent aussi avoir leur propre type.
Pour déclarer ces types et les types internes, on utilise le module standard de Python `typing`.
Ces types qui ont des types internes sont appelés des types «génériques» (**generic**). Et il est possible de les déclarer, même avec leurs types internes.
Il existe spécialement pour supporter ces annotations de types.
Pour déclarer ces types et les types internes, vous pouvez utiliser le module standard Python `typing`. Il existe spécifiquement pour supporter ces «type hints».
#### `List`
#### Versions plus récentes de Python { #newer-versions-of-python }
Par exemple, définissons une variable comme `list` de `str`.
La syntaxe utilisant `typing` est **compatible** avec toutes les versions, de Python 3.6 aux plus récentes, y compris Python 3.9, Python 3.10, etc.
Importez `List` (avec un `L` majuscule) depuis `typing`.
Au fur et à mesure que Python évolue, les **versions plus récentes** offrent un support amélioré pour ces annotations de type et, dans de nombreux cas, vous n'aurez même pas besoin d'importer et d'utiliser le module `typing` pour déclarer les annotations de type.
{*../../docs_src/python_types/tutorial006.py hl[1] *}
Si vous pouvez choisir une version plus récente de Python pour votre projet, vous pourrez profiter de cette simplicité supplémentaire.
Déclarez la variable, en utilisant la syntaxe des deux-points (`:`).
Dans toute la documentation, il y a des exemples compatibles avec chaque version de Python (quand il y a une différence).
Et comme type, mettez `List`.
Par exemple « **Python 3.6+** » signifie que c'est compatible avec Python 3.6 ou supérieur (y compris 3.7, 3.8, 3.9, 3.10, etc). Et « **Python 3.9+** » signifie que c'est compatible avec Python 3.9 ou supérieur (y compris 3.10, etc).
Les listes étant un type contenant des types internes, mettez ces derniers entre crochets (`[`, `]`) :
Si vous pouvez utiliser les **dernières versions de Python**, utilisez les exemples pour la version la plus récente, ceux-ci auront la **meilleure et la plus simple syntaxe**, par exemple, « **Python 3.10+** ».
{*../../docs_src/python_types/tutorial006.py hl[4] *}
#### List { #list }
/// tip | Astuce
Par exemple, définissons une variable comme une `list` de `str`.
Ces types internes entre crochets sont appelés des "paramètres de type".
Déclarez la variable, avec la même syntaxe de deux-points (`:`).
Ici, `str` est un paramètre de type passé à `List`.
Comme type, mettez `list`.
Comme la liste est un type qui contient des types internes, vous les mettez entre crochets :
{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
/// info
Ces types internes entre crochets sont appelés des «paramètres de type».
Dans ce cas, `str` est le paramètre de type passé à `list`.
///
Ce qui signifie : "la variable `items` est une `list`, et chacun de ses éléments a pour type `str`.
Cela signifie : « la variable `items` est une `list`, et chacun des éléments de cette liste est un `str` ».
En faisant cela, votre éditeur pourra vous aider, même pendant que vous traitez des éléments de la liste.
En faisant cela, votre éditeur peut fournir du support même pendant le traitement des éléments de la liste :
<img src="/img/python-types/image05.png">
Sans types, c'est presque impossible à réaliser.
Vous remarquerez que la variable `item` n'est qu'un des éléments de la list `items`.
Remarquez que la variable `item` est un des éléments de la liste `items`.
Et pourtant, l'éditeur sait qu'elle est de type `str` et pourra donc vous aider à l'utiliser.
Et pourtant, l'éditeur sait que c'est un `str`, et fournit du support pour ça.
#### `Tuple` et `Set`
#### Tuple et Set { #tuple-and-set }
C'est le même fonctionnement pour déclarer un `tuple` ou un `set` :
Vous feriez la même chose pour déclarer des `tuple` et des `set` :
{*../../docs_src/python_types/tutorial007.py hl[1,4] *}
{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
Dans cet exemple :
Cela signifie :
* La variable `items_t` est un `tuple` avec 3 éléments, un `int`, un deuxième `int`, et un `str`.
* La variable `items_t` est un `tuple` avec 3 éléments, un `int`, un autre `int`, et un `str`.
* La variable `items_s` est un `set`, et chacun de ses éléments est de type `bytes`.
#### `Dict`
#### Dict { #dict }
Pour définir un `dict`, il faut lui passer 2 paramètres, séparés par une virgule (`,`).
Pour définir un `dict`, vous passez 2 paramètres de type, séparés par des virgules.
Le premier paramètre de type est pour les clés et le second pour les valeurs du dictionnaire (`dict`).
Le premier paramètre de type est pour les clés du `dict`.
{*../../docs_src/python_types/tutorial008.py hl[1,4] *}
Le second paramètre de type est pour les valeurs du `dict` :
Dans cet exemple :
{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
* La variable `prices` est de type `dict` :
* Les clés de ce dictionnaire sont de type `str`.
* Les valeurs de ce dictionnaire sont de type `float`.
Cela signifie :
#### `Optional`
* La variable `prices` est un `dict` :
* Les clés de ce `dict` sont de type `str` (disons, le nom de chaque élément).
* Les valeurs de ce `dict` sont de type `float` (disons, le prix de chaque élément).
Vous pouvez aussi utiliser `Optional` pour déclarer qu'une variable a un type, comme `str` mais qu'il est "optionnel" signifiant qu'il pourrait aussi être `None`.
#### Union { #union }
{*../../docs_src/python_types/tutorial009.py hl[1,4] *}
Vous pouvez déclarer qu'une variable peut être de **plusieurs types**, par exemple, un `int` ou un `str`.
Utiliser `Optional[str]` plutôt que `str` permettra à l'éditeur de vous aider à détecter les erreurs où vous supposeriez qu'une valeur est toujours de type `str`, alors qu'elle pourrait aussi être `None`.
En Python 3.6 et supérieur (y compris Python 3.10) vous pouvez utiliser le type `Union` de `typing` et mettre entre crochets les types possibles à accepter.
#### Types génériques
En Python 3.10, il existe aussi une **nouvelle syntaxe** où vous pouvez mettre les types possibles séparés par une <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>barre verticale (`|`)</abbr>.
Les types qui peuvent contenir des paramètres de types entre crochets, comme :
//// tab | Python 3.10+
* `List`
* `Tuple`
* `Set`
* `Dict`
```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!}
```
////
Dans les deux cas, cela signifie que `item` pourrait être un `int` ou un `str`.
#### Possiblement `None` { #possibly-none }
Vous pouvez déclarer qu'une valeur pourrait avoir un type, comme `str`, mais qu'elle pourrait aussi être `None`.
En Python 3.6 et supérieur (y compris Python 3.10) vous pouvez le déclarer en important et en utilisant `Optional` depuis le module `typing`.
```Python hl_lines="1 4"
{!../../docs_src/python_types/tutorial009_py39.py!}
```
Utiliser `Optional[str]` au lieu de seulement `str` permettra à l'éditeur de vous aider à détecter des erreurs où vous pourriez supposer qu'une valeur est toujours un `str`, alors qu'elle pourrait aussi être `None`.
`Optional[Something]` est en fait un raccourci pour `Union[Something, None]`, ils sont équivalents.
Cela signifie aussi qu'en Python 3.10, vous pouvez utiliser `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 | Alternative Python 3.9+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
#### Utiliser `Union` ou `Optional` { #using-union-or-optional }
Si vous utilisez une version de Python inférieure à 3.10, voici une astuce de mon point de vue très **subjectif** :
* 🚨 Évitez d'utiliser `Optional[SomeType]`
* À la place ✨ **utilisez `Union[SomeType, None]`** ✨.
Les deux sont équivalents et, en dessous, c'est la même chose, mais je recommande `Union` plutôt que `Optional` parce que le mot «optional» semblerait impliquer que la valeur est optionnelle, et cela signifie en réalité «elle peut être `None` », même si elle n'est pas optionnelle et est toujours requise.
Je pense que `Union[SomeType, None]` est plus explicite sur ce que cela signifie.
Ce n'est qu'une question de mots et de noms. Mais ces mots peuvent affecter la façon dont vous et vos coéquipiers pensez au code.
Par exemple, prenons cette fonction :
{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
Le paramètre `name` est défini comme `Optional[str]`, mais il n'est **pas optionnel**, vous ne pouvez pas appeler la fonction sans le paramètre :
```Python
say_hi() # Oh, no, this throws an error! 😱
```
Le paramètre `name` est **toujours requis** (pas *optionnel*) parce qu'il n'a pas de valeur par défaut. Pourtant, `name` accepte `None` comme valeur :
```Python
say_hi(name=None) # This works, None is valid 🎉
```
La bonne nouvelle est qu'une fois que vous serez sur Python 3.10, vous n'aurez plus à vous en soucier, car vous pourrez simplement utiliser `|` pour définir des unions de types :
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
Et ensuite vous n'aurez plus à vous soucier de noms comme `Optional` et `Union`. 😎
#### Types génériques { #generic-types }
Ces types qui prennent des paramètres de type entre crochets sont appelés des **Generic types** ou **Generics**, par exemple :
//// tab | Python 3.10+
Vous pouvez utiliser les mêmes types intégrés comme generics (avec des crochets et des types à l'intérieur) :
* `list`
* `tuple`
* `set`
* `dict`
Et comme avec les versions précédentes de Python, depuis le module `typing` :
* `Union`
* `Optional`
* ...et d'autres.
sont appelés des **types génériques** ou **Generics**.
En Python 3.10, comme alternative à l'utilisation des generics `Union` et `Optional`, vous pouvez utiliser la <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>barre verticale (`|`)</abbr> pour déclarer des unions de types, c'est bien mieux et plus simple.
### Classes en tant que types
////
//// tab | Python 3.9+
Vous pouvez utiliser les mêmes types intégrés comme generics (avec des crochets et des types à l'intérieur) :
* `list`
* `tuple`
* `set`
* `dict`
Et des generics depuis le module `typing` :
* `Union`
* `Optional`
* ...et d'autres.
////
### Classes en tant que types { #classes-as-types }
Vous pouvez aussi déclarer une classe comme type d'une variable.
Disons que vous avez une classe `Person`, avec une variable `name` :
{*../../docs_src/python_types/tutorial010.py hl[1:3] *}
Disons que vous avez une classe `Person`, avec un nom :
{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
Vous pouvez ensuite déclarer une variable de type `Person` :
{*../../docs_src/python_types/tutorial010.py hl[6] *}
{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
Et vous aurez accès, encore une fois, au support complet offert par l'éditeur :
Et alors, encore une fois, vous obtenez tout le support de l'éditeur :
<img src="/img/python-types/image06.png">
## Les modèles Pydantic
Remarquez que cela signifie que « `one_person` est une **instance** de la classe `Person` ».
Cela ne signifie pas que « `one_person` est la **classe** appelée `Person` ».
## Les modèles Pydantic { #pydantic-models }
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> est une bibliothèque Python pour effectuer de la validation de données.
Vous déclarez la forme de la donnée avec des classes et des attributs.
Vous déclarez la « forme » de la donnée comme des classes avec des attributs.
Chaque attribut possède un type.
Et chaque attribut a un type.
Puis vous créez une instance de cette classe avec certaines valeurs et **Pydantic** validera les valeurs, les convertira dans le type adéquat (si c'est nécessaire et possible) et vous donnera un objet avec toute la donnée.
Ensuite vous créez une instance de cette classe avec certaines valeurs et elle validera les valeurs, les convertira dans le type approprié (si c'est le cas) et vous donnera un objet avec toutes les données.
Ainsi, votre éditeur vous offrira un support adapté pour l'objet résultant.
Et vous obtenez tout le support de l'éditeur avec cet objet résultant.
Extrait de la documentation officielle de **Pydantic** :
Un exemple des documents officiels de Pydantic :
{*../../docs_src/python_types/tutorial011.py*}
{* ../../docs_src/python_types/tutorial011_py310.py *}
/// info
Pour en savoir plus à propos de <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, allez jeter un coup d'oeil à sa documentation</a>.
Pour en savoir plus sur <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, consultez sa documentation</a>.
///
**FastAPI** est basé entièrement sur **Pydantic**.
**FastAPI** est entièrement basé sur Pydantic.
Vous verrez bien plus d'exemples de son utilisation dans [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}.
Vous verrez beaucoup plus tout cela en pratique dans le [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}.
## Les annotations de type dans **FastAPI**
/// tip | Astuce
**FastAPI** utilise ces annotations pour faire différentes choses.
Pydantic a un comportement spécial lorsque vous utilisez `Optional` ou `Union[Something, None]` sans valeur par défaut, vous pouvez en lire plus à ce sujet dans la documentation Pydantic sur les <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>.
Avec **FastAPI**, vous déclarez des paramètres grâce aux annotations de types et vous obtenez :
///
* **du support de l'éditeur**
* **de la vérification de types**
## Type Hints avec des annotations de métadonnées { #type-hints-with-metadata-annotations }
...et **FastAPI** utilise ces mêmes déclarations pour :
Python a aussi une fonctionnalité qui permet de mettre des **<abbr title="Data about the data, in this case, information about the type, e.g. a description.">métadonnées</abbr> supplémentaires** dans ces «type hints» en utilisant `Annotated`.
* **Définir les prérequis** : depuis les paramètres de chemins des requêtes, les entêtes, les corps, les dépendances, etc.
* **Convertir des données** : depuis la requête vers les types requis.
* **Valider des données** : venant de chaque requête :
* Générant automatiquement des **erreurs** renvoyées au client quand la donnée est invalide.
Depuis Python 3.9, `Annotated` fait partie de la bibliothèque standard, vous pouvez donc l'importer depuis `typing`.
{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
Python lui-même ne fait rien avec ce `Annotated`. Et pour les éditeurs et d'autres outils, le type est toujours `str`.
Mais vous pouvez utiliser cet espace dans `Annotated` pour fournir à **FastAPI** des métadonnées supplémentaires sur la manière dont vous voulez que votre application se comporte.
La chose importante à retenir est que **le premier *paramètre de type*** que vous passez à `Annotated` est le **vrai type**. Le reste n'est que des métadonnées pour d'autres outils.
Pour le moment, vous devez juste savoir que `Annotated` existe, et que c'est du Python standard. 😎
Plus tard, vous verrez à quel point cela peut être **puissant**.
/// tip | Astuce
Le fait que ce soit du **Python standard** signifie que vous aurez toujours la **meilleure expérience développeur possible** dans votre éditeur, avec les outils que vous utilisez pour analyser et refactoriser votre code, etc. ✨
Et aussi que votre code sera très compatible avec de nombreux autres outils et bibliothèques Python. 🚀
///
## Les annotations de type dans **FastAPI** { #type-hints-in-fastapi }
**FastAPI** tire parti de ces «type hints» pour faire plusieurs choses.
Avec **FastAPI** vous déclarez des paramètres avec des «type hints» et vous obtenez :
* **Support de l'éditeur**.
* **Vérifications de types**.
... et **FastAPI** utilise les mêmes déclarations pour :
* **Définir des exigences** : à partir des paramètres de chemin de la requête, des paramètres de requête, des en-têtes, des corps, des dépendances, etc.
* **Convertir les données** : de la requête vers le type requis.
* **Valider les données** : provenant de chaque requête :
* Générant des **erreurs automatiques** renvoyées au client lorsque les données sont invalides.
* **Documenter** l'API avec OpenAPI :
* ce qui ensuite utilisé par les interfaces utilisateur automatiques de documentation interactive.
* ce qui est ensuite utilisé par les interfaces utilisateur automatiques de documentation interactive.
Tout cela peut paraître bien abstrait, mais ne vous inquiétez pas, vous verrez tout ça en pratique dans [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}.
Tout cela peut sembler abstrait. Ne vous inquiétez pas. Vous verrez tout cela en action dans le [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}.
Ce qu'il faut retenir c'est qu'en utilisant les types standard de Python, à un seul endroit (plutôt que d'ajouter plus de classes, de décorateurs, etc.), **FastAPI** fera une grande partie du travail pour vous.
L'important est qu'en utilisant les types standards de Python, à un seul endroit (au lieu d'ajouter plus de classes, de décorateurs, etc.), **FastAPI** fera une grande partie du travail pour vous.
/// info
Si vous avez déjà lu le tutoriel et êtes revenus ici pour voir plus sur les types, une bonne ressource est la <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">"cheat sheet" de `mypy`</a>.
Si vous avez déjà parcouru tout le tutoriel et que vous êtes revenu pour en voir plus sur les types, une bonne ressource est <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">la « cheat sheet » de `mypy`</a>.
///

View File

@@ -1,84 +1,86 @@
# Tâches d'arrière-plan
# Tâches d'arrière-plan { #background-tasks }
Vous pouvez définir des tâches d'arrière-plan qui seront exécutées après avoir retourné une réponse.
Vous pouvez définir des tâches d'arrière-plan à exécuter *après* avoir renvoyé une réponse.
Ceci est utile pour les opérations qui doivent avoir lieu après une requête, mais le client n'a pas réellement besoin d'attendre que l'opération soit terminée pour recevoir une réponse.
Ceci est utile pour les opérations qui doivent avoir lieu après une requête, mais pour lesquelles le client n'a pas réellement besoin d'attendre que l'opération se termine avant de recevoir la réponse.
Cela comprend, par exemple :
* Les notifications par email envoyées après l'exécution d'une action :
* Étant donné que se connecter à un serveur et envoyer un email a tendance à être «lent» (plusieurs secondes), vous pouvez retourner la réponse directement et envoyer la notification en arrière-plan.
* Traiter des données :
* Par exemple, si vous recevez un fichier qui doit passer par un traitement lent, vous pouvez retourner une réponse «Accepted» (HTTP 202) puis faire le traitement en arrière-plan.
* Comme se connecter à un serveur email et envoyer un email a tendance à être « lent » (plusieurs secondes), vous pouvez renvoyer la réponse immédiatement et envoyer la notification par email en arrière-plan.
* Le traitement de données :
* Par exemple, disons que vous recevez un fichier qui doit passer par un traitement lent, vous pouvez renvoyer une réponse « Accepted » (HTTP 202) et traiter le fichier en arrière-plan.
## Utiliser `BackgroundTasks` { #using-backgroundtasks }
## Utiliser `BackgroundTasks`
Pour commencer, importez `BackgroundTasks` et définissez un paramètre dans votre *fonction de chemin d'accès* avec une déclaration de type `BackgroundTasks` :
Pour commencer, importez `BackgroundTasks` et définissez un paramètre dans votre *fonction de chemin* avec `BackgroundTasks` comme type déclaré.
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
**FastAPI** créera l'objet de type `BackgroundTasks` pour vous et le passera comme paramètre.
## Créer une fonction de tâche
## Créer une fonction de tâche { #create-a-task-function }
Une fonction à exécuter comme tâche d'arrière-plan est juste une fonction standard qui peut recevoir des paramètres.
Créez une fonction à exécuter comme tâche d'arrière-plan.
Elle peut être une fonction asynchrone (`async def`) ou une fonction normale (`def`), **FastAPI** saura la gérer correctement.
C'est simplement une fonction standard qui peut recevoir des paramètres.
Dans cet exemple, la fonction de tâche écrira dans un fichier (afin de simuler un envoi d'email).
Elle peut être une fonction `async def` ou une fonction `def` normale, **FastAPI** saura la gérer correctement.
L'opération d'écriture n'utilisant ni `async` ni `await`, on définit la fonction avec un `def` normal.
Dans ce cas, la fonction de tâche écrira dans un fichier (en simulant l'envoi d'un email).
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
Et comme l'opération d'écriture n'utilise pas `async` et `await`, nous définissons la fonction avec un `def` normal :
## Ajouter une tâche d'arrière-plan
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
Dans votre *fonction de chemin*, passez votre fonction de tâche à l'objet de type `BackgroundTasks` (`background_tasks` ici) grâce à la méthode `.add_task()` :
## Ajouter la tâche d'arrière-plan { #add-the-background-task }
À l'intérieur de votre *fonction de chemin d'accès*, passez votre fonction de tâche à l'objet de type *background tasks* avec la méthode `.add_task()` :
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
`.add_task()` reçoit comme arguments :
* Une fonction de tâche à exécuter en arrière-plan (`write_notification`).
* Les arguments positionnels à passer à la fonction de tâche dans l'ordre (`email`).
* Les arguments nommés à passer à la fonction de tâche (`message="some notification"`).
* N'importe quelle séquence d'arguments qui doit être passée à la fonction de tâche dans l'ordre (`email`).
* N'importe quels arguments nommés qui doivent être passés à la fonction de tâche (`message="some notification"`).
## Injection de dépendances
## Injection de dépendances { #dependency-injection }
Utiliser `BackgroundTasks` fonctionne aussi avec le système d'injection de dépendances. Vous pouvez déclarer un paramètre de type `BackgroundTasks` à différents niveaux : dans une *fonction de chemin*, dans une dépendance, dans une sous-dépendance...
Utiliser `BackgroundTasks` fonctionne aussi avec le système d'injection de dépendances, vous pouvez déclarer un paramètre de type `BackgroundTasks` à plusieurs niveaux : dans une *fonction de chemin d'accès*, dans une dépendance (dependable), dans une sous-dépendance, etc.
**FastAPI** sait quoi faire dans chaque cas et comment réutiliser le même objet, afin que tous les paramètres de type `BackgroundTasks` soient fusionnés et que les tâches soient exécutées en arrière-plan :
**FastAPI** sait quoi faire dans chaque cas et comment réutiliser le même objet, de sorte que toutes les tâches d'arrière-plan sont fusionnées et exécutées ensuite en arrière-plan :
{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *}
Dans cet exemple, les messages seront écrits dans le fichier `log.txt` après que la réponse soit envoyée.
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
S'il y avait une `query` (paramètre nommé `q`) dans la requête, alors elle sera écrite dans `log.txt` via une tâche d'arrière-plan.
Et ensuite une autre tâche d'arrière-plan (générée dans les paramètres de la *la fonction de chemin*) écrira un message dans `log.txt` comprenant le paramètre de chemin `email`.
Dans cet exemple, les messages seront écrits dans le fichier `log.txt` *après* que la réponse est envoyée.
## Détails techniques
S'il y avait une query dans la requête, elle sera écrite dans le log via une tâche d'arrière-plan.
Et ensuite une autre tâche d'arrière-plan générée au niveau de la *fonction de chemin d'accès* écrira un message en utilisant le paramètre de chemin `email`.
## Détails techniques { #technical-details }
La classe `BackgroundTasks` provient directement de <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>.
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`.
Elle est importée/incluse directement dans FastAPI afin que vous puissiez l'importer depuis `fastapi` et éviter d'importer accidentellement l'alternative `BackgroundTask` (sans le `s` à la fin) depuis `starlette.background`.
En utilisant seulement `BackgroundTasks` (et non `BackgroundTask`), il est possible de l'utiliser en tant que paramètre de *fonction de chemin* et de laisser **FastAPI** gérer le reste pour vous, comme en utilisant l'objet `Request` directement.
En utilisant uniquement `BackgroundTasks` (et non `BackgroundTask`), il est alors possible de l'utiliser comme paramètre de *fonction de chemin d'accès* et de laisser **FastAPI** gérer le reste pour vous, comme lorsque vous utilisez directement l'objet `Request`.
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.
Il est toujours possible d'utiliser `BackgroundTask` seul dans FastAPI, mais vous devez créer l'objet dans votre code et renvoyer une `Response` Starlette l'incluant.
Plus de détails sont disponibles dans <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">la documentation officielle de Starlette sur les tâches d'arrière-plan</a> (via leurs classes `BackgroundTasks`et `BackgroundTask`).
Vous pouvez voir plus de détails dans <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">la documentation officielle de Starlette sur les Background Tasks</a>.
## Avertissement
## Mise en garde { #caveat }
Si vous avez besoin de réaliser des traitements lourds en tâche d'arrière-plan et que vous n'avez pas besoin que ces traitements aient lieu dans le même process (par exemple, pas besoin de partager la mémoire, les variables, etc.), il peut s'avérer profitable d'utiliser des outils plus importants tels que <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>.
Si vous devez effectuer des calculs lourds en arrière-plan et que vous n'avez pas nécessairement besoin qu'ils soient exécutés par le même process (par exemple, vous n'avez pas besoin de partager la mémoire, les variables, etc.), vous pouvez tirer bénéfice d'utiliser d'autres outils plus importants comme <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>.
Ces outils nécessitent généralement des configurations plus complexes ainsi qu'un gestionnaire de queue de message, comme RabbitMQ ou Redis, mais ils permettent d'exécuter des tâches d'arrière-plan dans différents process, et potentiellement, sur plusieurs serveurs.
Ils ont tendance à nécessiter des configurations plus complexes, un gestionnaire de file de messages/tâches, comme RabbitMQ ou Redis, mais ils vous permettent d'exécuter des tâches d'arrière-plan dans plusieurs process, et surtout, sur plusieurs serveurs.
Mais si vous avez besoin d'accéder aux variables et objets de la même application **FastAPI**, ou si vous avez besoin d'effectuer de petites tâches d'arrière-plan (comme envoyer des notifications par email), vous pouvez simplement vous contenter d'utiliser `BackgroundTasks`.
Mais si vous devez accéder à des variables et objets de la même app **FastAPI**, ou si vous devez effectuer de petites tâches d'arrière-plan (comme envoyer une notification par email), vous pouvez simplement utiliser `BackgroundTasks`.
## Résumé
## Résumé { #recap }
Importez et utilisez `BackgroundTasks` grâce aux paramètres de *fonction de chemin* et les dépendances pour ajouter des tâches d'arrière-plan.
Importez et utilisez `BackgroundTasks` avec des paramètres dans les *fonctions de chemin d'accès* et les dépendances pour ajouter des tâches d'arrière-plan.

View File

@@ -1,24 +1,24 @@
# Body - Paramètres multiples
# Body - Paramètres multiples { #body-multiple-parameters }
Maintenant que nous avons vu comment manipuler `Path` et `Query`, voyons comment faire pour le corps d'une requête, communément désigné par le terme anglais "body".
Maintenant que nous avons vu comment utiliser `Path` et `Query`, voyons des usages plus avancés des déclarations du corps de la requête.
## Mélanger les paramètres `Path`, `Query` et body
## Mélanger les paramètres `Path`, `Query` et body { #mix-path-query-and-body-parameters }
Tout d'abord, sachez que vous pouvez mélanger les déclarations des paramètres `Path`, `Query` et body, **FastAPI** saura quoi faire.
Tout d'abord, bien entendu, vous pouvez mélanger librement les déclarations de paramètres `Path`, `Query` et du corps de la requête, et **FastAPI** saura quoi faire.
Vous pouvez également déclarer des paramètres body comme étant optionnels, en leur assignant une valeur par défaut à `None` :
Et vous pouvez également déclarer des paramètres body comme étant optionnels, en définissant la valeur par défaut à `None` :
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note
/// note | Remarque
Notez que, dans ce cas, le paramètre `item` provenant du `Body` est optionnel (sa valeur par défaut est `None`).
Notez que, dans ce cas, le `item` qui serait pris depuis le corps est optionnel, car il a une valeur par défaut à `None`.
///
## Paramètres multiples du body
## Paramètres multiples du body { #multiple-body-parameters }
Dans l'exemple précédent, les opérations de routage attendaient un body JSON avec les attributs d'un `Item`, par exemple :
Dans l'exemple précédent, les *chemins d'accès* s'attendraient à un corps JSON avec les attributs d'un `Item`, comme :
```JSON
{
@@ -29,13 +29,14 @@ Dans l'exemple précédent, les opérations de routage attendaient un body JSON
}
```
Mais vous pouvez également déclarer plusieurs paramètres provenant de body, par exemple `item` et `user` simultanément :
Mais vous pouvez aussi déclarer plusieurs paramètres body, par ex. `item` et `user` :
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
Dans ce cas, **FastAPI** détectera qu'il y a plus d'un paramètre dans le body (chacun correspondant à un modèle Pydantic).
Il utilisera alors les noms des paramètres comme clés, et s'attendra à recevoir quelque chose de semblable à :
Dans ce cas, **FastAPI** remarquera qu'il y a plus d'un paramètre body dans la fonction (il y a deux paramètres qui sont des modèles Pydantic).
Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le corps, et s'attendra à un corps comme :
```JSON
{
@@ -52,29 +53,30 @@ Il utilisera alors les noms des paramètres comme clés, et s'attendra à recevo
}
```
/// note
/// note | Remarque
"Notez que, bien que nous ayons déclaré le paramètre `item` de la même manière que précédemment, il est maintenant associé à la clé `item` dans le corps de la requête."`.
Notez que, même si le `item` a été déclaré de la même manière qu'avant, il est maintenant attendu dans le corps avec une clé `item`.
///
**FastAPI** effectue la conversion de la requête de façon transparente, de sorte que les objets `item` et `user` se trouvent correctement définis.
**FastAPI** fera la conversion automatique à partir de la requête, de sorte que le paramètre `item` reçoive son contenu spécifique, et de même pour `user`.
Il effectue également la validation des données (même imbriquées les unes dans les autres), et permet de les documenter correctement (schéma OpenAPI et documentation auto-générée).
Il effectuera la validation des données composées, et les documentera ainsi pour le schéma OpenAPI et les documents automatiques.
## Valeurs scalaires dans le body
## Valeurs scalaires dans le body { #singular-values-in-body }
De la même façon qu'il existe `Query` et `Path` pour définir des données supplémentaires pour les paramètres query et path, **FastAPI** fournit un équivalent `Body`.
De la même manière qu'il existe `Query` et `Path` pour définir des données supplémentaires pour les paramètres de requête et de chemin, **FastAPI** fournit un équivalent `Body`.
Par exemple, en étendant le modèle précédent, vous pouvez vouloir ajouter un paramètre `importance` dans le même body, en plus des paramètres `item` et `user`.
Par exemple, en étendant le modèle précédent, vous pourriez décider que vous voulez une autre clé `importance` dans le même corps, en plus de `item` et `user`.
Si vous le déclarez tel quel, comme c'est une valeur [scalaire](https://docs.github.com/fr/graphql/reference/scalars), **FastAPI** supposera qu'il s'agit d'un paramètre de requête (`Query`).
Si vous le déclarez tel quel, comme c'est une valeur scalaire, **FastAPI** supposera qu'il s'agit d'un paramètre de requête.
Mais vous pouvez indiquer à **FastAPI** de la traiter comme une variable de body en utilisant `Body` :
Mais vous pouvez indiquer à **FastAPI** de la traiter comme une autre clé dans le corps en utilisant `Body` :
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
Dans ce cas, **FastAPI** s'attendra à un body semblable à :
Dans ce cas, **FastAPI** s'attendra à un corps comme :
```JSON
{
@@ -92,19 +94,19 @@ Dans ce cas, **FastAPI** s'attendra à un body semblable à :
}
```
Encore une fois, cela convertira les types de données, les validera, permettra de générer la documentation, etc...
Là encore, il convertira les types de données, validera, documentera, etc.
## Paramètres multiples body et query
## Plusieurs paramètres body et query { #multiple-body-params-and-query }
Bien entendu, vous pouvez déclarer autant de paramètres que vous le souhaitez, en plus des paramètres body déjà déclarés.
Bien entendu, vous pouvez aussi déclarer des paramètres de requête supplémentaires dès que vous en avez besoin, en plus de n'importe quels paramètres body.
Par défaut, les valeurs [scalaires](https://docs.github.com/fr/graphql/reference/scalars) sont interprétées comme des paramètres query, donc inutile d'ajouter explicitement `Query`. Vous pouvez juste écrire :
Comme, par défaut, les valeurs scalaires sont interprétées comme des paramètres de requête, vous n'avez pas besoin d'ajouter explicitement un `Query`, vous pouvez simplement faire :
```Python
q: Union[str, None] = None
```
Ou bien, en Python 3.10 et supérieur :
Ou en Python 3.10 et supérieur :
```Python
q: str | None = None
@@ -112,31 +114,33 @@ q: str | None = None
Par exemple :
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *}
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
/// info
`Body` possède les mêmes paramètres de validation additionnels et de gestion des métadonnées que `Query` et `Path`, ainsi que d'autres que nous verrons plus tard.
/// info | info
`Body` possède aussi les mêmes paramètres supplémentaires de validation et de métadonnées que `Query`, `Path` et d'autres que vous verrez plus tard.
///
## Inclure un paramètre imbriqué dans le body
## Inclure un seul paramètre body { #embed-a-single-body-parameter }
Disons que vous avez seulement un paramètre `item` dans le body, correspondant à un modèle Pydantic `Item`.
Disons que vous n'avez qu'un seul paramètre body `item` issu d'un modèle Pydantic `Item`.
Par défaut, **FastAPI** attendra sa déclaration directement dans le body.
Par défaut, **FastAPI** s'attendra alors à son corps directement.
Cependant, si vous souhaitez qu'il interprête correctement un JSON avec une clé `item` associée au contenu du modèle, comme cela serait le cas si vous déclariez des paramètres body additionnels, vous pouvez utiliser le paramètre spécial `embed` de `Body` :
Mais si vous voulez qu'il s'attende à un JSON avec une clé `item` et, à l'intérieur, le contenu du modèle, comme il le fait lorsque vous déclarez des paramètres body supplémentaires, vous pouvez utiliser le paramètre spécial `embed` de `Body` :
```Python
item: Item = Body(embed=True)
```
Voici un exemple complet :
comme dans :
{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
Dans ce cas **FastAPI** attendra un body semblable à :
Dans ce cas **FastAPI** s'attendra à un corps comme :
```JSON hl_lines="2"
{
@@ -160,12 +164,12 @@ au lieu de :
}
```
## Pour résumer
## Récapitulatif { #recap }
Vous pouvez ajouter plusieurs paramètres body dans votre fonction de routage, même si une requête ne peut avoir qu'un seul body.
Vous pouvez ajouter plusieurs paramètres body à votre *fonction de chemin d'accès*, même si une requête ne peut avoir qu'un seul corps.
Cependant, **FastAPI** se chargera de faire opérer sa magie, afin de toujours fournir à votre fonction des données correctes, les validera et documentera le schéma associé.
Mais **FastAPI** le gérera, vous donnera les données correctes dans votre fonction, et validera et documentera le schéma correct dans le *chemin d'accès*.
Vous pouvez également déclarer des valeurs [scalaires](https://docs.github.com/fr/graphql/reference/scalars) à recevoir dans le body.
Vous pouvez aussi déclarer des valeurs scalaires à recevoir dans le corps.
Et vous pouvez indiquer à **FastAPI** d'inclure le body dans une autre variable, même lorsqu'un seul paramètre est déclaré.
Et vous pouvez indiquer à **FastAPI** d'inclure le corps dans une clé même lorsqu'il n'y a qu'un seul paramètre déclaré.

View File

@@ -1,40 +1,41 @@
# Corps de la requête
# Corps de la requête { #request-body }
Quand vous avez besoin d'envoyer de la donnée depuis un client (comme un navigateur) vers votre API, vous l'envoyez en tant que **corps de requête**.
Quand vous avez besoin d'envoyer des données depuis un client (disons, un navigateur) vers votre API, vous les envoyez en tant que **corps de la requête**.
Le corps d'une **requête** est de la donnée envoyée par le client à votre API. Le corps d'une **réponse** est la donnée envoyée par votre API au client.
Le corps d'une **requête** est des données envoyées par le client à votre API. Le corps d'une **réponse** est la donnée envoyée par votre API au client.
Votre API aura presque toujours à envoyer un corps de **réponse**. Mais un client n'a pas toujours à envoyer un corps de **requête**.
Votre API a presque toujours besoin d'envoyer un corps de **réponse**. Mais les clients n'ont pas nécessairement besoin d'envoyer des **corps de requête** en permanence, parfois ils demandent seulement un chemin, peut-être avec des paramètres de requête, mais n'envoient pas de corps.
Pour déclarer un corps de **requête**, on utilise les modèles de <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> en profitant de tous leurs avantages et fonctionnalités.
/// info
Pour envoyer de la donnée, vous devriez utiliser : `POST` (le plus populaire), `PUT`, `DELETE` ou `PATCH`.
Pour envoyer des données, vous devriez utiliser l'une des méthodes suivantes : `POST` (la plus courante), `PUT`, `DELETE` ou `PATCH`.
Envoyer un corps dans une requête `GET` a un comportement non défini dans les spécifications, cela est néanmoins supporté par **FastAPI**, seulement pour des cas d'utilisation très complexes/extrêmes.
Envoyer un corps avec une requête `GET` a un comportement non défini dans les spécifications ; néanmoins, cela est supporté par FastAPI, uniquement pour des cas d'utilisation très complexes/extrêmes.
Ceci étant découragé, la documentation interactive générée par Swagger UI ne montrera pas de documentation pour le corps d'une requête `GET`, et les proxys intermédiaires risquent de ne pas le supporter.
Comme cela est déconseillé, la documentation interactive avec Swagger UI n'affichera pas la documentation du corps lors de l'utilisation de `GET`, et les proxys intermédiaires risquent de ne pas le supporter.
///
## Importez le `BaseModel` de Pydantic
## Importer le `BaseModel` de Pydantic { #import-pydantics-basemodel }
Commencez par importer la classe `BaseModel` du module `pydantic` :
Commencez par importer `BaseModel` depuis `pydantic` :
{* ../../docs_src/body/tutorial001.py hl[4] *}
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
## Créez votre modèle de données
## Créer votre modèle de données { #create-your-data-model }
Déclarez ensuite votre modèle de données en tant que classe qui hérite de `BaseModel`.
Utilisez les types Python standard pour tous les attributs :
{* ../../docs_src/body/tutorial001.py hl[7:11] *}
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
Tout comme pour la déclaration de paramètres de requête, quand un attribut de modèle a une valeur par défaut, il n'est pas nécessaire. Sinon, cet attribut doit être renseigné dans le corps de la requête. Pour rendre ce champ optionnel simplement, utilisez `None` comme valeur par défaut.
Par exemple, le modèle ci-dessus déclare un "objet" JSON (ou `dict` Python) tel que :
Tout comme pour la déclaration de paramètres de requête, quand un attribut de modèle a une valeur par défaut, il n'est pas requis. Sinon, il est requis. Utilisez `None` pour le rendre simplement optionnel.
Par exemple, le modèle ci-dessus déclare un « `object` » JSON (ou `dict` Python) tel que :
```JSON
{
@@ -45,7 +46,7 @@ Par exemple, le modèle ci-dessus déclare un "objet" JSON (ou `dict` Python) te
}
```
...`description` et `tax` étant des attributs optionnels (avec `None` comme valeur par défaut), cet "objet" JSON serait aussi valide :
... comme `description` et `tax` sont optionnels (avec une valeur par défaut de `None`), cet « `object` » JSON serait aussi valide :
```JSON
{
@@ -54,109 +55,112 @@ Par exemple, le modèle ci-dessus déclare un "objet" JSON (ou `dict` Python) te
}
```
## Déclarez-le comme paramètre
## Le déclarer comme paramètre { #declare-it-as-a-parameter }
Pour l'ajouter à votre *opération de chemin*, déclarez-le comme vous déclareriez des paramètres de chemin ou de requête :
Pour l'ajouter à votre *chemin d'accès*, déclarez-le de la même manière que vous avez déclaré les paramètres de chemin et de requête :
{* ../../docs_src/body/tutorial001.py hl[18] *}
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
...et déclarez que son type est le modèle que vous avez créé : `Item`.
... et déclarez son type comme étant le modèle que vous avez créé, `Item`.
## Résultats
## Résultats { #results }
En utilisant uniquement les déclarations de type Python, **FastAPI** réussit à :
Avec simplement cette déclaration de type Python, **FastAPI** va :
* Lire le contenu de la requête en tant que JSON.
* Lire le corps de la requête en tant que JSON.
* Convertir les types correspondants (si nécessaire).
* Valider la donnée.
* Si la donnée est invalide, une erreur propre et claire sera renvoyée, indiquant exactement où était la donnée incorrecte.
* Passer la donnée reçue dans le paramètre `item`.
* Ce paramètre ayant été déclaré dans la fonction comme étant de type `Item`, vous aurez aussi tout le support offert par l'éditeur (auto-complétion, etc.) pour tous les attributs de ce paramètre et les types de ces attributs.
* Générer des définitions <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> pour votre modèle, qui peuvent être utilisées où vous en avez besoin dans votre projet ensuite.
* Ces schémas participeront à la constitution du schéma généré OpenAPI, et seront donc utilisés par les documentations automatiquement générées.
* Valider les données.
* Si les données sont invalides, il renverra une erreur propre et claire, indiquant exactement où et quelles étaient les données incorrectes.
* Vous donner les données reçues dans le paramètre `item`.
* Comme vous l'avez déclaré dans la fonction comme étant de type `Item`, vous aurez aussi tout le support offert par l'éditeur (autocomplétion, etc.) pour tous les attributs et leurs types.
* Générer des définitions <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> pour votre modèle ; vous pouvez aussi les utiliser ailleurs si cela a du sens pour votre projet.
* Ces schémas feront partie du schéma OpenAPI généré et seront utilisés par la documentation automatique <abbr title="User Interfaces - Interfaces utilisateur">UIs</abbr>.
## Documentation automatique
## Documentation automatique { #automatic-docs }
Les schémas JSON de vos modèles seront intégrés au schéma OpenAPI global de votre application, et seront donc affichés dans la documentation interactive de l'API :
Les JSON Schemas de vos modèles feront partie du schéma OpenAPI généré de votre application, et seront affichés dans la documentation interactive de l'API :
<img src="/img/tutorial/body/image01.png">
Et seront aussi utilisés dans chaque *opération de chemin* de la documentation utilisant ces modèles :
Et seront aussi utilisés dans la documentation de l'API au sein de chaque *chemin d'accès* qui en a besoin :
<img src="/img/tutorial/body/image02.png">
## Support de l'éditeur
## Support de l'éditeur { #editor-support }
Dans votre éditeur, vous aurez des annotations de types et de l'auto-complétion partout dans votre fonction (ce qui n'aurait pas été le cas si vous aviez utilisé un classique `dict` plutôt qu'un modèle Pydantic) :
Dans votre éditeur, à l'intérieur de votre fonction, vous obtiendrez des annotations de type et de la complétion partout (cela n'arriverait pas si vous receviez un `dict` au lieu d'un modèle Pydantic) :
<img src="/img/tutorial/body/image03.png">
Et vous obtenez aussi de la vérification d'erreur pour les opérations incorrectes de types :
Vous obtenez aussi des vérifications d'erreur pour les opérations incorrectes de types :
<img src="/img/tutorial/body/image04.png">
Ce n'est pas un hasard, ce framework entier a été bâti avec ce design comme objectif.
Ce n'est pas un hasard, ce framework entier a été bâti autour de ce design.
Et cela a été rigoureusement testé durant la phase de design, avant toute implémentation, pour s'assurer que cela fonctionnerait avec tous les éditeurs.
Et cela a été rigoureusement testé durant la phase de design, avant toute implémentation, pour vous assurer que cela fonctionnerait avec tous les éditeurs.
Des changements sur Pydantic ont même été faits pour supporter cela.
Des changements ont même été faits sur Pydantic lui-même pour supporter cela.
Les captures d'écrans précédentes ont été prises sur <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>.
Les captures d'écrans précédentes ont été prises avec <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>.
Mais vous auriez le même support de l'éditeur avec <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> et la majorité des autres éditeurs de code Python.
Mais vous auriez le même support de l'éditeur avec <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> et la plupart des autres éditeurs Python :
<img src="/img/tutorial/body/image05.png">
/// tip | Astuce
Si vous utilisez <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> comme éditeur, vous pouvez utiliser le Plugin <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
Si vous utilisez <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> comme éditeur, vous pouvez utiliser le <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
Ce qui améliore le support pour les modèles Pydantic avec :
Il améliore le support de l'éditeur pour les modèles Pydantic, avec :
* de l'auto-complétion
* des vérifications de type
* du "refactoring" (ou remaniement de code)
* de la recherche
* de l'inspection
* autocomplétion
* vérifications de type
* refactoring
* recherche
* inspections
///
## Utilisez le modèle
## Utiliser le modèle { #use-the-model }
Dans la fonction, vous pouvez accéder à tous les attributs de l'objet du modèle directement :
À l'intérieur de la fonction, vous pouvez accéder directement à tous les attributs de l'objet du modèle :
{* ../../docs_src/body/tutorial002.py hl[21] *}
{* ../../docs_src/body/tutorial002_py310.py *}
## Corps de la requête + paramètres de chemin
## Corps de la requête + paramètres de chemin { #request-body-path-parameters }
Vous pouvez déclarer des paramètres de chemin et un corps de requête pour la même *opération de chemin*.
Vous pouvez déclarer des paramètres de chemin et le corps de la requête en même temps.
**FastAPI** est capable de reconnaître que les paramètres de la fonction qui correspondent aux paramètres de chemin doivent être **récupérés depuis le chemin**, et que les paramètres de fonctions déclarés comme modèles Pydantic devraient être **récupérés depuis le corps de la requête**.
**FastAPI** reconnaîtra que les paramètres de la fonction qui correspondent aux paramètres de chemin doivent être **récupérés depuis le chemin**, et que les paramètres de fonction déclarés comme modèles Pydantic doivent être **récupérés depuis le corps de la requête**.
{* ../../docs_src/body/tutorial003.py hl[17:18] *}
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
## Corps de la requête + paramètres de chemin et de requête
Vous pouvez aussi déclarer un **corps**, et des paramètres de **chemin** et de **requête** dans la même *opération de chemin*.
## Corps de la requête + paramètres de chemin et de requête { #request-body-path-query-parameters }
**FastAPI** saura reconnaître chacun d'entre eux et récupérer la bonne donnée au bon endroit.
Vous pouvez aussi déclarer des paramètres de **corps**, de **chemin** et de **requête**, tous en même temps.
{* ../../docs_src/body/tutorial004.py hl[18] *}
**FastAPI** reconnaîtra chacun d'entre eux et récupérera les données au bon endroit.
Les paramètres de la fonction seront reconnus comme tel :
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
Les paramètres de la fonction seront reconnus comme suit :
* Si le paramètre est aussi déclaré dans le **chemin**, il sera utilisé comme paramètre de chemin.
* Si le paramètre est d'un **type singulier** (comme `int`, `float`, `str`, `bool`, etc.), il sera interprété comme un paramètre de **requête**.
* Si le paramètre est déclaré comme ayant pour type un **modèle Pydantic**, il sera interprété comme faisant partie du **corps** de la requête.
* Si le paramètre est déclaré comme étant du type d'un **modèle Pydantic**, il sera interprété comme faisant partie du **corps** de la requête.
/// note
/// note | Remarque
**FastAPI** saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `=None`.
FastAPI saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `= None`.
Le type `Optional` dans `Optional[str]` n'est pas utilisé par **FastAPI**, mais sera utile à votre éditeur pour améliorer le support offert par ce dernier et détecter plus facilement des erreurs de type.
Le `str | None` (Python 3.10+) ou `Union` dans `Union[str, None]` (Python 3.9+) n'est pas utilisé par FastAPI pour déterminer que la valeur n'est pas requise ; il saura qu'elle n'est pas requise parce qu'elle a une valeur par défaut `= None`.
Mais ajouter les annotations de type permettra à votre éditeur de vous offrir un meilleur support et de détecter des erreurs.
///
## Sans Pydantic
## Sans Pydantic { #without-pydantic }
Si vous ne voulez pas utiliser des modèles Pydantic, vous pouvez aussi utiliser des paramètres de **Corps**. Pour cela, allez voir la partie de la documentation sur [Corps de la requête - Paramètres multiples](body-multiple-params.md){.internal-link target=_blank}.
Si vous ne voulez pas utiliser de modèles Pydantic, vous pouvez aussi utiliser des paramètres **Body**. Consultez les documents pour [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.

View File

@@ -1,14 +1,14 @@
# <abbr title="En anglais: Debugging">Débogage</abbr>
# Débogage { #debugging }
Vous pouvez connecter le <abbr title="En anglais: debugger">débogueur</abbr> dans votre éditeur, par exemple avec Visual Studio Code ou PyCharm.
Vous pouvez connecter le débogueur dans votre éditeur, par exemple avec Visual Studio Code ou PyCharm.
## Faites appel à `uvicorn`
## Faites appel à `uvicorn` { #call-uvicorn }
Dans votre application FastAPI, importez et exécutez directement `uvicorn` :
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
### À propos de `__name__ == "__main__"`
### À propos de `__name__ == "__main__"` { #about-name-main }
Le but principal de `__name__ == "__main__"` est d'avoir du code qui est exécuté lorsque votre fichier est appelé avec :
@@ -26,7 +26,7 @@ mais qui n'est pas appelé lorsqu'un autre fichier l'importe, comme dans :
from myapp import app
```
#### Pour davantage de détails
#### Pour davantage de détails { #more-details }
Imaginons que votre fichier s'appelle `myapp.py`.
@@ -54,12 +54,12 @@ va s'exécuter.
Cela ne se produira pas si vous importez ce module (fichier).
Par exemple, si vous avez un autre fichier `importer.py` qui contient :
Ainsi, si vous avez un autre fichier `importer.py` qui contient :
```Python
from myapp import app
# Code supplémentaire
# Some more code
```
dans ce cas, la variable automatique `__name__` à l'intérieur de `myapp.py` n'aura pas la valeur `"__main__"`.
@@ -78,18 +78,18 @@ Pour plus d'informations, consultez <a href="https://docs.python.org/3/library/_
///
## Exécutez votre code avec votre <abbr title="En anglais: debugger">débogueur</abbr>
## Exécutez votre code avec votre débogueur { #run-your-code-with-your-debugger }
Parce que vous exécutez le serveur Uvicorn directement depuis votre code, vous pouvez appeler votre programme Python (votre application FastAPI) directement depuis le <abbr title="En anglais: debugger">débogueur</abbr>.
Parce que vous exécutez le serveur Uvicorn directement depuis votre code, vous pouvez appeler votre programme Python (votre application FastAPI) directement depuis le débogueur.
---
Par exemple, dans Visual Studio Code, vous pouvez :
- Cliquer sur l'onglet "Debug" de la barre d'activités de Visual Studio Code.
- "Add configuration...".
- Sélectionnez "Python".
- Lancez le <abbr title="En anglais: debugger">débogueur</abbr> avec l'option "`Python: Current File (Integrated Terminal)`".
* Aller dans le panneau « Debug ».
* « Add configuration... ».
* Sélectionner « Python »
* Lancer le débogueur avec l'option « `Python: Current File (Integrated Terminal)` ».
Il démarrera alors le serveur avec votre code **FastAPI**, s'arrêtera à vos points d'arrêt, etc.
@@ -101,10 +101,10 @@ Voici à quoi cela pourrait ressembler :
Si vous utilisez Pycharm, vous pouvez :
- Ouvrir le menu "Run".
- Sélectionnez l'option "Debug...".
- Un menu contextuel s'affiche alors.
- Sélectionnez le fichier à déboguer (dans ce cas, `main.py`).
* Ouvrir le menu « Run ».
* Sélectionner l'option « Debug... ».
* Un menu contextuel s'affiche alors.
* Sélectionner le fichier à déboguer (dans ce cas, `main.py`).
Il démarrera alors le serveur avec votre code **FastAPI**, s'arrêtera à vos points d'arrêt, etc.

View File

@@ -1,107 +1,122 @@
# Démarrage
# Premiers pas { #first-steps }
Le fichier **FastAPI** le plus simple possible pourrait ressembler à cela :
Le fichier **FastAPI** le plus simple possible pourrait ressembler à ceci :
{* ../../docs_src/first_steps/tutorial001.py *}
{* ../../docs_src/first_steps/tutorial001_py39.py *}
Copiez ce code dans un fichier nommé `main.py`.
Copiez cela dans un fichier `main.py`.
Démarrez le serveur :
Lancez le serveur de développement :
<div class="termy">
```console
$ uvicorn main:app --reload
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
/// note
La commande `uvicorn main:app` fait référence à :
* `main` : le fichier `main.py` (le module Python).
* `app` : l'objet créé dans `main.py` via la ligne `app = FastAPI()`.
* `--reload` : l'option disant à uvicorn de redémarrer le serveur à chaque changement du code. À ne pas utiliser en production !
///
Vous devriez voir dans la console, une ligne semblable à la suivante :
Dans la sortie, il y a une ligne qui ressemble à :
```hl_lines="4"
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
Cette ligne montre l'URL par laquelle l'app est actuellement accessible, sur votre machine locale.
Cette ligne affiche lURL à laquelle votre app est servie, sur votre machine locale.
### Allez voir le résultat
### Vérifiez { #check-it }
Ouvrez votre navigateur à l'adresse <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Ouvrez votre navigateur à ladresse <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Vous obtiendrez cette réponse JSON :
Vous verrez la réponse JSON :
```JSON
{"message": "Hello World"}
```
### Documentation interactive de l'API
### Documentation interactive de lAPI { #interactive-api-docs }
Rendez-vous sur <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Rendez-vous maintenant sur <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Vous verrez la documentation interactive de l'API générée automatiquement (via <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>) :
Vous verrez la documentation interactive de lAPI générée automatiquement (fournie par <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>) :
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Documentation alternative
### Documentation alternative de lAPI { #alternative-api-docs }
Ensuite, rendez-vous sur <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Et maintenant, rendez-vous sur <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Vous y verrez la documentation alternative (via <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>) :
Vous verrez la documentation alternative générée automatiquement (fournie par <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>) :
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
### OpenAPI
### OpenAPI { #openapi }
**FastAPI** génère un "schéma" contenant toute votre API dans le standard de définition d'API **OpenAPI**.
**FastAPI** génère un « schéma » contenant toute votre API en utilisant le standard **OpenAPI** pour définir les API.
#### "Schéma"
#### « Schéma » { #schema }
Un "schéma" est une définition ou une description de quelque chose. Pas le code qui l'implémente, uniquement une description abstraite.
Un « schéma » est une définition ou une description de quelque chose. Pas le code qui limplémente, mais simplement une description abstraite.
#### "Schéma" d'API
#### « Schéma » dAPI { #api-schema }
Ici, <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> est une spécification qui dicte comment définir le schéma de votre API.
Dans ce cas, <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> est une spécification qui dicte comment définir un schéma de votre API.
Le schéma inclut les chemins de votre API, les paramètres potentiels de chaque chemin, etc.
Cette définition de schéma inclut les chemins de votre API, les paramètres possibles quils acceptent, etc.
#### "Schéma" de données
#### « Schéma » de données { #data-schema }
Le terme "schéma" peut aussi faire référence à la forme de la donnée, comme un contenu JSON.
Le terme « schéma » peut aussi faire référence à la structure de certaines données, comme un contenu JSON.
Dans ce cas, cela signifierait les attributs JSON, ainsi que les types de ces attributs, etc.
Dans ce cas, cela signifierait les attributs JSON, et les types de données quils ont, etc.
#### OpenAPI et JSON Schema
#### OpenAPI et JSON Schema { #openapi-and-json-schema }
**OpenAPI** définit un schéma d'API pour votre API. Il inclut des définitions (ou "schémas") de la donnée envoyée et reçue par votre API en utilisant **JSON Schema**, le standard des schémas de données JSON.
OpenAPI définit un schéma dAPI pour votre API. Et ce schéma inclut des définitions (ou des « schémas ») des données envoyées et reçues par votre API en utilisant **JSON Schema**, le standard des schémas de données JSON.
#### Allez voir `openapi.json`
#### Vérifiez le `openapi.json` { #check-the-openapi-json }
Si vous êtes curieux d'à quoi ressemble le schéma brut **OpenAPI**, **FastAPI** génère automatiquement un (schéma) JSON avec les descriptions de toute votre API.
Si vous êtes curieux de voir à quoi ressemble le schéma OpenAPI brut, FastAPI génère automatiquement un (schéma) JSON avec les descriptions de toute votre API.
Vous pouvez le voir directement à cette adresse : <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
Le schéma devrait ressembler à ceci :
Vous pouvez le voir directement à ladresse : <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
Il affichera un JSON commençant par quelque chose comme :
```JSON
{
"openapi": "3.0.2",
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
@@ -120,79 +135,87 @@ Le schéma devrait ressembler à ceci :
...
```
#### À quoi sert OpenAPI
#### À quoi sert OpenAPI { #what-is-openapi-for }
Le schéma **OpenAPI** est ce qui alimente les deux systèmes de documentation interactive.
Le schéma OpenAPI est ce qui alimente les deux systèmes de documentation interactive inclus.
Et il existe des dizaines d'alternatives, toutes basées sur **OpenAPI**. Vous pourriez facilement ajouter n'importe laquelle de ces alternatives à votre application **FastAPI**.
Et il existe des dizaines dalternatives, toutes basées sur OpenAPI. Vous pourriez facilement ajouter nimporte laquelle de ces alternatives à votre application construite avec **FastAPI**.
Vous pourriez aussi l'utiliser pour générer du code automatiquement, pour les clients qui communiquent avec votre API. Comme par exemple, des applications frontend, mobiles ou IOT.
Vous pourriez aussi lutiliser pour générer du code automatiquement, pour les clients qui communiquent avec votre API. Par exemple, des applications frontend, mobiles ou IoT.
## Récapitulatif, étape par étape
### Déployer votre app (optionnel) { #deploy-your-app-optional }
### Étape 1 : import `FastAPI`
Vous pouvez, de manière optionnelle, déployer votre app FastAPI sur <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a> ; allez vous inscrire sur la liste dattente si ce nest pas déjà fait. 🚀
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
Si vous avez déjà un compte **FastAPI Cloud** (nous vous avons invité depuis la liste dattente 😉), vous pouvez déployer votre application avec une seule commande.
`FastAPI` est une classe Python qui fournit toutes les fonctionnalités nécessaires au lancement de votre API.
Avant de déployer, vous devez vous assurer que vous êtes connecté :
<div class="termy">
```console
$ fastapi login
You are logged in to FastAPI Cloud 🚀
```
</div>
Ensuite, déployez votre app :
<div class="termy">
```console
$ fastapi deploy
Deploying to FastAPI Cloud...
✅ Deployment successful!
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
```
</div>
Cest tout ! Vous pouvez maintenant accéder à votre app à cette URL. ✨
## Récapitulatif, étape par étape { #recap-step-by-step }
### Étape 1 : importer `FastAPI` { #step-1-import-fastapi }
{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *}
`FastAPI` est une classe Python qui fournit toutes les fonctionnalités pour votre API.
/// note | Détails techniques
`FastAPI` est une classe héritant directement de `Starlette`.
`FastAPI` est une classe qui hérite directement de `Starlette`.
Vous pouvez donc aussi utiliser toutes les fonctionnalités de <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> depuis `FastAPI`.
Vous pouvez aussi utiliser toutes les fonctionnalités de <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> avec `FastAPI`.
///
### Étape 2 : créer une "instance" `FastAPI`
### Étape 2 : créer une « instance » `FastAPI` { #step-2-create-a-fastapi-instance }
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *}
Ici la variable `app` sera une "instance" de la classe `FastAPI`.
Ici, la variable `app` sera une « instance » de la classe `FastAPI`.
Ce sera le point principal d'interaction pour créer toute votre API.
Ce sera le point principal dinteraction pour créer toute votre API.
Cette `app` est la même que celle à laquelle fait référence `uvicorn` dans la commande :
### Étape 3 : créer un *chemin d'accès* { #step-3-create-a-path-operation }
<div class="termy">
#### Chemin { #path }
```console
$ uvicorn main:app --reload
« Path » fait référence ici à la dernière partie de lURL à partir du premier `/`.
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Si vous créez votre app avec :
{* ../../docs_src/first_steps/tutorial002.py hl[3] *}
Et la mettez dans un fichier `main.py`, alors vous appelleriez `uvicorn` avec :
<div class="termy">
```console
$ uvicorn main:my_awesome_api --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### Étape 3: créer une *opération de chemin*
#### Chemin
Chemin, ou "path" fait référence ici à la dernière partie de l'URL démarrant au premier `/`.
Donc, dans un URL tel que :
Donc, dans une URL comme :
```
https://example.com/items/foo
```
...le "path" serait :
... le « path » serait :
```
/items/foo
@@ -200,66 +223,67 @@ https://example.com/items/foo
/// info
Un chemin, ou "path" est aussi souvent appelé route ou "endpoint".
Un « path » est aussi communément appelé un « endpoint » ou une « route ».
///
#### Opération
Lors de la construction dune API, le « path » est la principale façon de séparer les « préoccupations » et les « ressources ».
"Opération" fait référence à une des "méthodes" HTTP.
#### Opération { #operation }
Une de :
« Opération » fait référence ici à lune des « méthodes » HTTP.
Lune de :
* `POST`
* `GET`
* `PUT`
* `DELETE`
...ou une des plus exotiques :
... et les plus exotiques :
* `OPTIONS`
* `HEAD`
* `PATCH`
* `TRACE`
Dans le protocol HTTP, vous pouvez communiquer avec chaque chemin en utilisant une (ou plus) de ces "méthodes".
Dans le protocole HTTP, vous pouvez communiquer avec chaque chemin en utilisant une (ou plusieurs) de ces « méthodes ».
---
En construisant des APIs, vous utilisez généralement ces méthodes HTTP spécifiques pour effectuer une action précise.
Lors de la construction dAPI, vous utilisez normalement ces méthodes HTTP spécifiques pour effectuer une action précise.
Généralement vous utilisez :
Normalement vous utilisez :
* `POST` : pour créer de la donnée.
* `GET` : pour lire de la donnée.
* `PUT` : pour mettre à jour de la donnée.
* `DELETE` : pour supprimer de la donnée.
* `POST` : pour créer des données.
* `GET` : pour lire des données.
* `PUT` : pour mettre à jour des données.
* `DELETE` : pour supprimer des données.
Donc, dans **OpenAPI**, chaque méthode HTTP est appelée une "opération".
Donc, dans OpenAPI, chacune des méthodes HTTP est appelée une « opération ».
Nous allons donc aussi appeler ces dernières des "**opérations**".
Nous allons aussi les appeler des « **opérations** ».
#### Définir un *décorateur de chemin d'accès* { #define-a-path-operation-decorator }
#### Définir un *décorateur d'opération de chemin*
{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *}
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
Le `@app.get("/")` dit à **FastAPI** que la fonction en dessous est chargée de gérer les requêtes qui vont sur :
Le `@app.get("/")` indique à **FastAPI** que la fonction juste en dessous est chargée de gérer les requêtes qui vont vers :
* le chemin `/`
* en utilisant une <abbr title="une méthode GET HTTP">opération <code>get</code></abbr>
* en utilisant une <abbr title="an HTTP GET method">opération <code>get</code></abbr>
/// info | `@décorateur` Info
/// info | `@decorator` Info
Cette syntaxe `@something` en Python est appelée un "décorateur".
Cette syntaxe `@something` en Python est appelée un « décorateur ».
Vous la mettez au dessus d'une fonction. Comme un joli chapeau décoratif (j'imagine que ce terme vient de là 🤷🏻‍♂).
Vous le mettez au-dessus dune fonction. Comme un joli chapeau décoratif (jimagine que cest de là que le terme vient).
Un "décorateur" prend la fonction en dessous et en fait quelque chose.
Un « décorateur » prend la fonction en dessous et fait quelque chose avec elle.
Dans notre cas, ce décorateur dit à **FastAPI** que la fonction en dessous correspond au **chemin** `/` avec l'**opération** `get`.
Dans notre cas, ce décorateur indique à **FastAPI** que la fonction en dessous correspond au **chemin** `/` avec une **opération** `get`.
C'est le "**décorateur d'opération de chemin**".
Cest le « **décorateur de chemin d'accès** ».
///
@@ -269,7 +293,7 @@ Vous pouvez aussi utiliser les autres opérations :
* `@app.put()`
* `@app.delete()`
Tout comme celles les plus exotiques :
Et les plus exotiques :
* `@app.options()`
* `@app.head()`
@@ -278,58 +302,79 @@ Tout comme celles les plus exotiques :
/// tip | Astuce
Vous êtes libres d'utiliser chaque opération (méthode HTTP) comme vous le désirez.
Vous êtes libre dutiliser chaque opération (méthode HTTP) comme vous le souhaitez.
**FastAPI** n'impose pas de sens spécifique à chacune d'elle.
**FastAPI** nimpose aucun sens spécifique.
Les informations qui sont présentées ici forment une directive générale, pas des obligations.
Les informations présentées ici servent de guide, pas dexigence.
Par exemple, quand l'on utilise **GraphQL**, toutes les actions sont effectuées en utilisant uniquement des opérations `POST`.
Par exemple, quand vous utilisez GraphQL, vous effectuez normalement toutes les actions en utilisant uniquement des opérations `POST`.
///
### Étape 4 : définir la **fonction de chemin**.
### Étape 4 : définir la **fonction de chemin d'accès** { #step-4-define-the-path-operation-function }
Voici notre "**fonction de chemin**" (ou fonction d'opération de chemin) :
Voici notre « **fonction de chemin d'accès** » :
* **chemin** : `/`.
* **opération** : `get`.
* **fonction** : la fonction sous le "décorateur" (sous `@app.get("/")`).
* **chemin** : est `/`.
* **opération** : est `get`.
* **fonction** : est la fonction sous le « décorateur » (sous `@app.get("/")`).
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *}
C'est une fonction Python.
Cest une fonction Python.
Elle sera appelée par **FastAPI** quand une requête sur l'URL `/` sera reçue via une opération `GET`.
Elle sera appelée par **FastAPI** chaque fois quil recevra une requête vers lURL « `/` » en utilisant une opération `GET`.
Ici, c'est une fonction asynchrone (définie avec `async def`).
Dans ce cas, cest une fonction `async`.
---
Vous pourriez aussi la définir comme une fonction classique plutôt qu'avec `async def` :
Vous pourriez aussi la définir comme une fonction normale au lieu de `async def` :
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
/// note
/// note | Remarque
Si vous ne connaissez pas la différence, allez voir la section [Concurrence : *"Vous êtes pressés ?"*](../async.md#vous-etes-presses){.internal-link target=_blank}.
Si vous ne connaissez pas la différence, consultez [Async : *« Pressé ? »*](../async.md#in-a-hurry){.internal-link target=_blank}.
///
### Étape 5 : retourner le contenu
### Étape 5 : retourner le contenu { #step-5-return-the-content }
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *}
Vous pouvez retourner un dictionnaire (`dict`), une liste (`list`), des valeurs seules comme des chaines de caractères (`str`) et des entiers (`int`), etc.
Vous pouvez retourner un `dict`, `list`, des valeurs seules comme `str`, `int`, etc.
Vous pouvez aussi retourner des models **Pydantic** (qui seront détaillés plus tard).
Vous pouvez aussi retourner des modèles Pydantic (vous en verrez plus à ce sujet plus tard).
Il y a de nombreux autres objets et modèles qui seront automatiquement convertis en JSON. Essayez d'utiliser vos favoris, il est fort probable qu'ils soient déjà supportés.
Il y a de nombreux autres objets et modèles qui seront automatiquement convertis en JSON (y compris les ORM, etc). Essayez dutiliser vos favoris, il est très probable quils soient déjà pris en charge.
## Récapitulatif
### Étape 6 : le déployer { #step-6-deploy-it }
Déployez votre app sur **<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** avec une seule commande : `fastapi deploy`. 🎉
#### À propos de FastAPI Cloud { #about-fastapi-cloud }
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** est construit par le même auteur et la même équipe derrière **FastAPI**.
Il simplifie le processus de **construction**, de **déploiement** et d**accès** à une API avec un effort minimal.
Il apporte la même **developer experience** de construction dapps avec FastAPI au **déploiement** dans le cloud. 🎉
FastAPI Cloud est le sponsor principal et le fournisseur de financement pour les projets open source *FastAPI and friends*. ✨
#### Déployer sur dautres fournisseurs cloud { #deploy-to-other-cloud-providers }
FastAPI est open source et basé sur des standards. Vous pouvez déployer des apps FastAPI sur nimporte quel fournisseur cloud de votre choix.
Suivez les guides de votre fournisseur cloud pour y déployer des apps FastAPI. 🤓
## Récapitulatif { #recap }
* Importez `FastAPI`.
* Créez une instance d'`app`.
* Ajoutez une **décorateur d'opération de chemin** (tel que `@app.get("/")`).
* Ajoutez une **fonction de chemin** (telle que `def root(): ...` comme ci-dessus).
* Lancez le serveur de développement (avec `uvicorn main:app --reload`).
* Créez une instance `app`.
* Écrivez un **décorateur de chemin d'accès** en utilisant des décorateurs comme `@app.get("/")`.
* Définissez une **fonction de chemin d'accès** ; par exemple, `def root(): ...`.
* Lancez le serveur de développement avec la commande `fastapi dev`.
* Déployez votre app de manière optionnelle avec `fastapi deploy`.

View File

@@ -1,83 +1,95 @@
# Tutoriel - Guide utilisateur - Introduction
# Tutoriel - Guide utilisateur { #tutorial-user-guide }
Ce tutoriel vous montre comment utiliser **FastAPI** avec la plupart de ses fonctionnalités, étape par étape.
Chaque section s'appuie progressivement sur les précédentes, mais elle est structurée de manière à séparer les sujets, afin que vous puissiez aller directement à l'un d'entre eux pour résoudre vos besoins spécifiques en matière d'API.
Il est également conçu pour fonctionner comme une référence future.
Il est également conçu pour fonctionner comme une référence future afin que vous puissiez revenir et voir exactement ce dont vous avez besoin.
Vous pouvez donc revenir et voir exactement ce dont vous avez besoin.
## Exécuter le code
## Exécuter le code { #run-the-code }
Tous les blocs de code peuvent être copiés et utilisés directement (il s'agit en fait de fichiers Python testés).
Pour exécuter l'un de ces exemples, copiez le code dans un fichier `main.py`, et commencez `uvicorn` avec :
Pour exécuter l'un de ces exemples, copiez le code dans un fichier `main.py`, et démarrez `fastapi dev` avec :
<div class="termy">
```console
$ uvicorn main:app --reload
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
Il est **FORTEMENT encouragé** que vous écriviez ou copiez le code, l'éditiez et l'exécutiez localement.
Il est **FORTEMENT encouragé** que vous écriviez ou copiiez le code, l'éditiez et l'exécutiez localement.
L'utiliser dans votre éditeur est ce qui vous montre vraiment les avantages de FastAPI, en voyant le peu de code que vous avez à écrire, toutes les vérifications de type, l'autocomplétion, etc.
---
## Installer FastAPI
## Installer FastAPI { #install-fastapi }
La première étape consiste à installer FastAPI.
Pour le tutoriel, vous voudrez peut-être l'installer avec toutes les dépendances et fonctionnalités optionnelles :
Vous devez vous assurer de créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis **d'installer FastAPI** :
<div class="termy">
```console
$ pip install fastapi[all]
$ pip install "fastapi[standard]"
---> 100%
```
</div>
... qui comprend également `uvicorn`, que vous pouvez utiliser comme serveur pour exécuter votre code.
/// note | Remarque
/// note
Lorsque vous installez avec `pip install "fastapi[standard]"`, cela inclut certaines dépendances standard optionnelles par défaut, notamment `fastapi-cloud-cli`, qui vous permet de déployer sur <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
Vous pouvez également l'installer pièce par pièce.
Si vous ne voulez pas avoir ces dépendances optionnelles, vous pouvez installer `pip install fastapi` à la place.
C'est ce que vous feriez probablement une fois que vous voudrez déployer votre application en production :
```
pip install fastapi
```
Installez également `uvicorn` pour qu'il fonctionne comme serveur :
```
pip install uvicorn
```
Et la même chose pour chacune des dépendances facultatives que vous voulez utiliser.
Si vous voulez installer les dépendances standard mais sans le `fastapi-cloud-cli`, vous pouvez installer avec `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
///
## Guide utilisateur avancé
## Guide utilisateur avancé { #advanced-user-guide }
Il existe également un **Guide d'utilisation avancé** que vous pouvez lire plus tard après ce **Tutoriel - Guide d'utilisation**.
Il existe également un **Guide utilisateur avancé** que vous pouvez lire plus tard après ce **Tutoriel - Guide utilisateur**.
Le **Guide d'utilisation avancé**, qui s'appuie sur cette base, utilise les mêmes concepts et vous apprend quelques fonctionnalités supplémentaires.
Le **Guide utilisateur avancé** s'appuie sur celui-ci, utilise les mêmes concepts et vous apprend quelques fonctionnalités supplémentaires.
Mais vous devez d'abord lire le **Tutoriel - Guide d'utilisation** (ce que vous êtes en train de lire en ce moment).
Mais vous devez d'abord lire le **Tutoriel - Guide utilisateur** (ce que vous êtes en train de lire en ce moment).
Il est conçu pour que vous puissiez construire une application complète avec seulement le **Tutoriel - Guide d'utilisation**, puis l'étendre de différentes manières, en fonction de vos besoins, en utilisant certaines des idées supplémentaires du **Guide d'utilisation avancé**.
Il est conçu pour que vous puissiez construire une application complète avec seulement le **Tutoriel - Guide utilisateur**, puis l'étendre de différentes manières, en fonction de vos besoins, en utilisant certaines des idées supplémentaires du **Guide utilisateur avancé**.

View File

@@ -1,8 +1,8 @@
# Paramètres de chemin et validations numériques
# Paramètres de chemin et validations numériques { #path-parameters-and-numeric-validations }
De la même façon que vous pouvez déclarer plus de validations et de métadonnées pour les paramètres de requête avec `Query`, vous pouvez déclarer le même type de validations et de métadonnées pour les paramètres de chemin avec `Path`.
## Importer Path
## Importer `Path` { #import-path }
Tout d'abord, importez `Path` de `fastapi`, et importez `Annotated` :
@@ -12,29 +12,29 @@ Tout d'abord, importez `Path` de `fastapi`, et importez `Annotated` :
FastAPI a ajouté le support pour `Annotated` (et a commencé à le recommander) dans la version 0.95.0.
Si vous avez une version plus ancienne, vous obtiendrez des erreurs en essayant d'utiliser `Annotated`.
Si vous avez une version plus ancienne, vous obtiendriez des erreurs en essayant d'utiliser `Annotated`.
Assurez-vous de [Mettre à jour la version de FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} à la version 0.95.1 à minima avant d'utiliser `Annotated`.
Assurez-vous de [Mettre à niveau la version de FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} à la version 0.95.1 à minima avant d'utiliser `Annotated`.
///
## Déclarer des métadonnées
## Déclarer des métadonnées { #declare-metadata }
Vous pouvez déclarer les mêmes paramètres que pour `Query`.
Vous pouvez déclarer tous les mêmes paramètres que pour `Query`.
Par exemple, pour déclarer une valeur de métadonnée `title` pour le paramètre de chemin `item_id`, vous pouvez écrire :
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
/// note
/// note | Remarque
Un paramètre de chemin est toujours requis car il doit faire partie du chemin. Même si vous l'avez déclaré avec `None` ou défini une valeur par défaut, cela ne changerait rien, il serait toujours requis.
///
## Ordonnez les paramètres comme vous le souhaitez
## Ordonner les paramètres selon vos besoins { #order-the-parameters-as-you-need }
/// tip
/// tip | Astuce
Ce n'est probablement pas aussi important ou nécessaire si vous utilisez `Annotated`.
@@ -46,23 +46,23 @@ Et vous n'avez pas besoin de déclarer autre chose pour ce paramètre, donc vous
Mais vous avez toujours besoin d'utiliser `Path` pour le paramètre de chemin `item_id`. Et vous ne voulez pas utiliser `Annotated` pour une raison quelconque.
Python se plaindra si vous mettez une valeur avec une "défaut" avant une valeur qui n'a pas de "défaut".
Python se plaindra si vous mettez une valeur avec une valeur « par défaut » avant une valeur qui n'a pas de valeur « par défaut ».
Mais vous pouvez les réorganiser, et avoir la valeur sans défaut (le paramètre de requête `q`) en premier.
Mais vous pouvez les réorganiser, et avoir la valeur sans valeur par défaut (le paramètre de requête `q`) en premier.
Cela n'a pas d'importance pour **FastAPI**. Il détectera les paramètres par leurs noms, types et déclarations par défaut (`Query`, `Path`, etc), il ne se soucie pas de l'ordre.
Ainsi, vous pouvez déclarer votre fonction comme suit :
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
Mais gardez à l'esprit que si vous utilisez `Annotated`, vous n'aurez pas ce problème, cela n'aura pas d'importance car vous n'utilisez pas les valeurs par défaut des paramètres de fonction pour `Query()` ou `Path()`.
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py hl[10] *}
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
## Ordonnez les paramètres comme vous le souhaitez (astuces)
## Ordonner les paramètres selon vos besoins, astuces { #order-the-parameters-as-you-need-tricks }
/// tip
/// tip | Astuce
Ce n'est probablement pas aussi important ou nécessaire si vous utilisez `Annotated`.
@@ -77,38 +77,29 @@ Si vous voulez :
* les avoir dans un ordre différent
* ne pas utiliser `Annotated`
...Python a une petite syntaxe spéciale pour cela.
... Python a une petite syntaxe spéciale pour cela.
Passez `*`, comme premier paramètre de la fonction.
Python ne fera rien avec ce `*`, mais il saura que tous les paramètres suivants doivent être appelés comme arguments "mots-clés" (paires clé-valeur), également connus sous le nom de <abbr title="De : K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Même s'ils n'ont pas de valeur par défaut.
Python ne fera rien avec ce `*`, mais il saura que tous les paramètres suivants doivent être appelés comme arguments par mot-clé (paires clé-valeur), également connus sous le nom de <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Même s'ils n'ont pas de valeur par défaut.
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
# Avec `Annotated`
### Mieux avec `Annotated` { #better-with-annotated }
Gardez à l'esprit que si vous utilisez `Annotated`, comme vous n'utilisez pas les valeurs par défaut des paramètres de fonction, vous n'aurez pas ce problème, et vous n'aurez probablement pas besoin d'utiliser `*`.
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
## Validations numériques : supérieur ou égal
## Validations numériques : supérieur ou égal { #number-validations-greater-than-or-equal }
Avec `Query` et `Path` (et d'autres que vous verrez plus tard) vous pouvez déclarer des contraintes numériques.
Ici, avec `ge=1`, `item_id` devra être un nombre entier "`g`reater than or `e`qual" à `1`.
Ici, avec `ge=1`, `item_id` devra être un nombre entier « `g`reater than or `e`qual » à `1`.
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
## Validations numériques : supérieur ou égal et inférieur ou égal
La même chose s'applique pour :
* `gt` : `g`reater `t`han
* `le` : `l`ess than or `e`qual
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
## Validations numériques : supérieur et inférieur ou égal
## Validations numériques : supérieur et inférieur ou égal { #number-validations-greater-than-and-less-than-or-equal }
La même chose s'applique pour :
@@ -117,7 +108,7 @@ La même chose s'applique pour :
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
## Validations numériques : flottants, supérieur et inférieur
## Validations numériques : flottants, supérieur et inférieur { #number-validations-floats-greater-than-and-less-than }
Les validations numériques fonctionnent également pour les valeurs `float`.
@@ -129,9 +120,9 @@ Et la même chose pour <abbr title="less than"><code>lt</code></abbr>.
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
## Pour résumer
## Pour résumer { #recap }
Avec `Query`, `Path` (et d'autres que vous verrez plus tard) vous pouvez déclarer des métadonnées et des validations de chaînes de la même manière qu'avec les [Paramètres de requête et validations de chaînes](query-params-str-validations.md){.internal-link target=_blank}.
Avec `Query`, `Path` (et d'autres que vous n'avez pas encore vus) vous pouvez déclarer des métadonnées et des validations de chaînes de la même manière qu'avec les [Paramètres de requête et validations de chaînes](query-params-str-validations.md){.internal-link target=_blank}.
Et vous pouvez également déclarer des validations numériques :
@@ -144,7 +135,7 @@ Et vous pouvez également déclarer des validations numériques :
`Query`, `Path`, et d'autres classes que vous verrez plus tard sont des sous-classes d'une classe commune `Param`.
Tous partagent les mêmes paramètres pour des validations supplémentaires et des métadonnées que vous avez vu précédemment.
Tous partagent les mêmes paramètres pour des validations supplémentaires et des métadonnées que vous avez vus.
///

View File

@@ -1,205 +1,196 @@
# Paramètres de chemin
# Paramètres de chemin { #path-parameters }
Vous pouvez déclarer des "paramètres" ou "variables" de chemin avec la même syntaxe que celle utilisée par le
<a href="https://docs.python.org/fr/3/library/string.html#format-string-syntax" class="external-link" target="_blank">formatage de chaîne Python</a> :
Vous pouvez déclarer des « paramètres » ou « variables » de chemin avec la même syntaxe que celle utilisée par les chaînes de format Python :
{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
La valeur du paramètre de chemin `item_id` sera transmise à votre fonction comme argument `item_id`.
La valeur du paramètre `item_id` sera transmise à la fonction dans l'argument `item_id`.
Donc, si vous exécutez cet exemple et allez sur <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>,
vous verrez comme réponse :
Donc, si vous exécutez cet exemple et allez sur <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, vous verrez comme réponse :
```JSON
{"item_id":"foo"}
```
## Paramètres de chemin typés
## Paramètres de chemin typés { #path-parameters-with-types }
Vous pouvez déclarer le type d'un paramètre de chemin dans la fonction, en utilisant les annotations de type Python :
Vous pouvez déclarer le type d'un paramètre de chemin dans la fonction, en utilisant les annotations de type standard de Python :
{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
Dans ce cas, `item_id` est déclaré comme `int`.
Ici, `item_id` est déclaré comme `int`.
/// check | Vérifications
/// check | vérifier
Ceci vous permettra d'obtenir des fonctionnalités de l'éditeur dans votre fonction, telles
que des vérifications d'erreur, de l'auto-complétion, etc.
Cela vous donnera le support de l'éditeur dans votre fonction, avec des vérifications d'erreurs, l'autocomplétion, etc.
///
## <abbr title="aussi appelé sérialisation, ou parfois parsing ou marshalling en anglais">Conversion</abbr> de données
## <abbr title="aussi connu sous le nom de : serialization, parsing, marshalling">Conversion</abbr> de données { #data-conversion }
Si vous exécutez cet exemple et allez sur <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, vous aurez comme réponse :
Si vous exécutez cet exemple et ouvrez votre navigateur sur <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, vous verrez comme réponse :
```JSON
{"item_id":3}
```
/// check | vérifier
/// check | Vérifications
Comme vous l'avez remarqué, la valeur reçue par la fonction (et renvoyée ensuite) est `3`,
en tant qu'entier (`int`) Python, pas la chaîne de caractères (`string`) `"3"`.
Remarquez que la valeur reçue par votre fonction (et renvoyée) est `3`, comme un `int` Python, pas une chaîne « 3 ».
Grâce aux déclarations de types, **FastAPI** fournit du
<abbr title="conversion de la chaîne de caractères venant de la requête HTTP en données Python">"parsing"</abbr> automatique.
Ainsi, avec cette déclaration de type, **FastAPI** vous fournit le <abbr title="conversion de la chaîne provenant d'une requête HTTP en données Python">« parsing »</abbr> automatique de la requête.
///
## Validation de données
## Validation de données { #data-validation }
Si vous allez sur <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, vous aurez une belle erreur HTTP :
Mais si vous allez dans le navigateur sur <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, vous verrez une belle erreur 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"
}
]
}
```
car le paramètre de chemin `item_id` possède comme valeur `"foo"`, qui ne peut pas être convertie en entier (`int`).
car le paramètre de chemin `item_id` avait la valeur « foo », qui n'est pas un `int`.
La même erreur se produira si vous passez un nombre flottant (`float`) et non un entier, comme ici
<a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>.
La même erreur apparaîtrait si vous fournissiez un `float` au lieu d'un `int`, comme ici : <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
/// check | Vérifications
/// check | vérifier
Ainsi, avec la même déclaration de type Python, **FastAPI** vous fournit une validation de données.
Donc, avec ces mêmes déclarations de type Python, **FastAPI** vous fournit de la validation de données.
Remarquez que l'erreur indique aussi clairement le point exact où la validation n'est pas passée.
Notez que l'erreur mentionne le point exact où la validation n'a pas réussi.
Ce qui est incroyablement utile au moment de développer et débugger du code qui interagit avec votre API.
C'est incroyablement utile lors du développement et du débogage de code qui interagit avec votre API.
///
## Documentation
## Documentation { #documentation }
Et quand vous vous rendez sur <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, vous verrez la
documentation générée automatiquement et interactive :
Et lorsque vous ouvrez votre navigateur sur <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, vous verrez une documentation d'API automatique et interactive comme :
<img src="/img/tutorial/path-params/image01.png">
/// info
/// check | Vérifications
À nouveau, en utilisant uniquement les déclarations de type Python, **FastAPI** vous fournit automatiquement une documentation interactive (via Swagger UI).
Encore une fois, uniquement avec cette même déclaration de type Python, **FastAPI** vous fournit une documentation automatique et interactive (intégrant Swagger UI).
On voit bien dans la documentation que `item_id` est déclaré comme entier.
Remarquez que le paramètre de chemin est déclaré comme un entier.
///
## Les avantages d'avoir une documentation basée sur une norme, et la documentation alternative.
## Avantages basés sur les standards, documentation alternative { #standards-based-benefits-alternative-documentation }
Le schéma généré suivant la norme <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a>,
il existe de nombreux outils compatibles.
Et comme le schéma généré provient du standard <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a>, il existe de nombreux outils compatibles.
Grâce à cela, **FastAPI** lui-même fournit une documentation alternative (utilisant ReDoc), qui peut être lue
sur <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a> :
Grâce à cela, **FastAPI** lui-même fournit une documentation d'API alternative (utilisant ReDoc), à laquelle vous pouvez accéder sur <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a> :
<img src="/img/tutorial/path-params/image02.png">
De la même façon, il existe bien d'autres outils compatibles, y compris des outils de génération de code
pour de nombreux langages.
De la même façon, il existe de nombreux outils compatibles. Y compris des outils de génération de code pour de nombreux langages.
## Pydantic
## Pydantic { #pydantic }
Toute la validation de données est effectué en arrière-plan avec <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>,
dont vous bénéficierez de tous les avantages. Vous savez donc que vous êtes entre de bonnes mains.
Toute la validation de données est effectuée en arrière-plan par <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, vous bénéficiez donc de tous ses avantages. Et vous savez que vous êtes entre de bonnes mains.
## L'ordre importe
Vous pouvez utiliser les mêmes déclarations de type avec `str`, `float`, `bool` et de nombreux autres types de données complexes.
Quand vous créez des *fonctions de chemins*, vous pouvez vous retrouver dans une situation où vous avez un chemin fixe.
Plusieurs d'entre eux sont abordés dans les prochains chapitres du tutoriel.
Tel que `/users/me`, disons pour récupérer les données sur l'utilisateur actuel.
## L'ordre importe { #order-matters }
Et vous avez un second chemin : `/users/{user_id}` pour récupérer de la donnée sur un utilisateur spécifique grâce à son identifiant d'utilisateur
Lors de la création de *chemins d'accès*, vous pouvez vous retrouver dans des situations où vous avez un chemin fixe.
Les *fonctions de chemin* étant évaluées dans l'ordre, il faut s'assurer que la fonction correspondant à `/users/me` est déclarée avant celle de `/users/{user_id}` :
Comme `/users/me`, disons que c'est pour obtenir des données sur l'utilisateur actuel.
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
Et vous pouvez aussi avoir un chemin `/users/{user_id}` pour obtenir des données sur un utilisateur spécifique via un identifiant utilisateur.
Sinon, le chemin `/users/{user_id}` correspondrait aussi à `/users/me`, la fonction "croyant" qu'elle a reçu un paramètre `user_id` avec pour valeur `"me"`.
Comme les *chemins d'accès* sont évalués dans l'ordre, vous devez vous assurer que le chemin pour `/users/me` est déclaré avant celui pour `/users/{user_id}` :
## Valeurs prédéfinies
{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
Si vous avez une *fonction de chemin* qui reçoit un *paramètre de chemin*, mais que vous voulez que les valeurs possibles des paramètres soient prédéfinies, vous pouvez utiliser les <abbr title="Enumeration">`Enum`</abbr> de Python.
Sinon, le chemin pour `/users/{user_id}` correspondrait aussi à `/users/me`, en « pensant » qu'il reçoit un paramètre `user_id` avec la valeur « me ».
### Création d'un `Enum`
De la même manière, vous ne pouvez pas redéfinir un chemin d'accès :
Importez `Enum` et créez une sous-classe qui hérite de `str` et `Enum`.
{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
En héritant de `str` la documentation sera capable de savoir que les valeurs doivent être de type `string` et pourra donc afficher cette `Enum` correctement.
Le premier sera toujours utilisé puisque le chemin correspond en premier.
Créez ensuite des attributs de classe avec des valeurs fixes, qui seront les valeurs autorisées pour cette énumération.
## Valeurs prédéfinies { #predefined-values }
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
Si vous avez un *chemin d'accès* qui reçoit un *paramètre de chemin*, mais que vous voulez que les valeurs valides possibles du *paramètre de chemin* soient prédéfinies, vous pouvez utiliser un <abbr title="Enumeration">`Enum`</abbr> Python standard.
/// info
### Créer une classe `Enum` { #create-an-enum-class }
<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">Les énumérations (ou enums) sont disponibles en Python</a> depuis la version 3.4.
Importez `Enum` et créez une sous-classe qui hérite de `str` et de `Enum`.
///
En héritant de `str`, les documents de l'API pourront savoir que les valeurs doivent être de type `string` et pourront les afficher correctement.
Créez ensuite des attributs de classe avec des valeurs fixes, qui seront les valeurs valides disponibles :
{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
/// tip | Astuce
Pour ceux qui se demandent, "AlexNet", "ResNet", et "LeNet" sont juste des noms de <abbr title="Techniquement, des architectures de modèles">modèles</abbr> de Machine Learning.
Si vous vous demandez, « AlexNet », « ResNet » et « LeNet » sont simplement des noms de <abbr title="Technically, Deep Learning model architectures">modèles</abbr> de Machine Learning.
///
### Déclarer un paramètre de chemin
### Déclarer un *paramètre de chemin* { #declare-a-path-parameter }
Créez ensuite un *paramètre de chemin* avec une annotation de type désignant l'énumération créée précédemment (`ModelName`) :
Créez ensuite un *paramètre de chemin* avec une annotation de type utilisant la classe enum que vous avez créée (`ModelName`) :
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
### Documentation
### Vérifier la documentation { #check-the-docs }
Les valeurs disponibles pour le *paramètre de chemin* sont bien prédéfinies, la documentation les affiche correctement :
Comme les valeurs disponibles pour le *paramètre de chemin* sont prédéfinies, la documentation interactive peut bien les afficher :
<img src="/img/tutorial/path-params/image03.png">
### Manipuler les *énumérations* Python
### Travailler avec les *énumérations* Python { #working-with-python-enumerations }
La valeur du *paramètre de chemin* sera un des "membres" de l'énumération.
La valeur du *paramètre de chemin* sera un *membre d'énumération*.
#### Comparer les *membres d'énumération*
#### Comparer des *membres d'énumération* { #compare-enumeration-members }
Vous pouvez comparer ce paramètre avec les membres de votre énumération `ModelName` :
Vous pouvez la comparer avec le *membre d'énumération* de votre enum créée `ModelName` :
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
#### Récupérer la *valeur de l'énumération*
#### Obtenir la *valeur de l'énumération* { #get-the-enumeration-value }
Vous pouvez obtenir la valeur réel d'un membre (une chaîne de caractères ici), avec `model_name.value`, ou en général, `votre_membre_d'enum.value` :
Vous pouvez obtenir la valeur réelle (un `str` dans ce cas) en utilisant `model_name.value`, ou de manière générale, `your_enum_member.value` :
{* ../../docs_src/path_params/tutorial005.py hl[20] *}
{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
/// tip | Astuce
Vous pouvez aussi accéder la valeur `"lenet"` avec `ModelName.lenet.value`.
Vous pouvez aussi accéder à la valeur « lenet » avec `ModelName.lenet.value`.
///
#### Retourner des *membres d'énumération*
#### Retourner des *membres d'énumération* { #return-enumeration-members }
Vous pouvez retourner des *membres d'énumération* dans vos *fonctions de chemin*, même imbriquée dans un JSON (e.g. un `dict`).
Vous pouvez retourner des *membres d'enum* depuis votre *chemin d'accès*, même imbriqués dans un corps JSON (par ex. un `dict`).
Ils seront convertis vers leurs valeurs correspondantes (chaînes de caractères ici) avant d'être transmis au client :
Ils seront convertis en leurs valeurs correspondantes (des chaînes dans ce cas) avant de les renvoyer au client :
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
Le client recevra une réponse JSON comme celle-ci :
Dans votre client, vous obtiendrez une réponse JSON comme :
```JSON
{
@@ -208,53 +199,53 @@ Le client recevra une réponse JSON comme celle-ci :
}
```
## Paramètres de chemin contenant des chemins
## Paramètres de chemin contenant des chemins { #path-parameters-containing-paths }
Disons que vous avez une *fonction de chemin* liée au chemin `/files/{file_path}`.
Disons que vous avez un *chemin d'accès* avec le chemin `/files/{file_path}`.
Mais que `file_path` lui-même doit contenir un *chemin*, comme `home/johndoe/myfile.txt` par exemple.
Mais vous avez besoin que `file_path` lui-même contienne un *chemin*, comme `home/johndoe/myfile.txt`.
Donc, l'URL pour ce fichier pourrait être : `/files/home/johndoe/myfile.txt`.
Donc, l'URL pour ce fichier serait quelque chose comme : `/files/home/johndoe/myfile.txt`.
### Support d'OpenAPI
### Support d'OpenAPI { #openapi-support }
OpenAPI ne supporte pas de manière de déclarer un paramètre de chemin contenant un *chemin*, cela pouvant causer des scénarios difficiles à tester et définir.
OpenAPI ne supporte pas une manière de déclarer qu'un *paramètre de chemin* contient un *chemin* à l'intérieur, car cela pourrait conduire à des scénarios difficiles à tester et à définir.
Néanmoins, cela reste faisable dans **FastAPI**, via les outils internes de Starlette.
Néanmoins, vous pouvez quand même le faire dans **FastAPI**, en utilisant un des outils internes de Starlette.
Et la documentation fonctionne quand même, bien qu'aucune section ne soit ajoutée pour dire que la paramètre devrait contenir un *chemin*.
Et les documents fonctionneraient quand même, bien qu'ils n'ajoutent aucune documentation indiquant que le paramètre devrait contenir un chemin.
### Convertisseur de *chemin*
### Convertisseur de chemin { #path-convertor }
En utilisant une option de Starlette directement, vous pouvez déclarer un *paramètre de chemin* contenant un *chemin* avec une URL comme :
En utilisant une option directement depuis Starlette, vous pouvez déclarer un *paramètre de chemin* contenant un *chemin* avec une URL comme :
```
/files/{file_path:path}
```
Dans ce cas, le nom du paramètre est `file_path`, et la dernière partie, `:path`, indique à Starlette que le paramètre devrait correspondre à un *chemin*.
Dans ce cas, le nom du paramètre est `file_path`, et la dernière partie, `:path`, indique que le paramètre doit correspondre à n'importe quel *chemin*.
Vous pouvez donc l'utilisez comme tel :
Ainsi, vous pouvez l'utiliser avec :
{* ../../docs_src/path_params/tutorial004.py hl[6] *}
{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
/// tip | Astuce
Vous pourriez avoir besoin que le paramètre contienne `/home/johndoe/myfile.txt`, avec un slash au début (`/`).
Vous pourriez avoir besoin que le paramètre contienne `/home/johndoe/myfile.txt`, avec un slash initial (`/`).
Dans ce cas, l'URL serait : `/files//home/johndoe/myfile.txt`, avec un double slash (`//`) entre `files` et `home`.
///
## Récapitulatif
## Récapitulatif { #recap }
Avec **FastAPI**, en utilisant les déclarations de type rapides, intuitives et standards de Python, vous bénéficiez de :
Avec **FastAPI**, en utilisant des déclarations de type Python courtes, intuitives et standard, vous obtenez :
* Support de l'éditeur : vérification d'erreurs, auto-complétion, etc.
* <abbr title="conversion de la chaîne de caractères venant de la requête HTTP en données Python">"Parsing"</abbr> de données.
* Validation de données.
* Annotations d'API et documentation automatique.
* Support de l'éditeur : vérifications d'erreurs, autocomplétion, etc.
* « parsing » de données <abbr title="conversion de la chaîne provenant d'une requête HTTP en données Python">parsing</abbr>
* Validation de données
* Annotation de l'API et documentation automatique
Et vous n'avez besoin de le déclarer qu'une fois.
Et vous n'avez à les déclarer qu'une seule fois.
C'est probablement l'avantage visible principal de **FastAPI** comparé aux autres *frameworks* (outre les performances pures).
C'est probablement le principal avantage visible de **FastAPI** comparé aux frameworks alternatifs (en dehors de la performance brute).

View File

@@ -1,166 +1,273 @@
# Paramètres de requête et validations de chaînes de caractères
# Paramètres de requête et validations de chaînes de caractères { #query-parameters-and-string-validations }
**FastAPI** vous permet de déclarer des informations et des validateurs additionnels pour vos paramètres de requêtes.
**FastAPI** vous permet de déclarer des informations supplémentaires et de la validation pour vos paramètres.
Commençons avec cette application pour exemple :
Prenons cette application comme exemple :
{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *}
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
Le paramètre de requête `q` a pour type `Union[str, None]` (ou `str | None` en Python 3.10), signifiant qu'il est de type `str` mais pourrait aussi être égal à `None`, et bien sûr, la valeur par défaut est `None`, donc **FastAPI** saura qu'il n'est pas requis.
Le paramètre de requête `q` est de type `str | None`, ce qui signifie quil est de type `str` mais pourrait aussi être `None`, et en effet, la valeur par défaut est `None`, donc FastAPI saura quil nest pas requis.
/// note
/// note | Remarque
**FastAPI** saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `= None`.
FastAPI saura que la valeur de `q` nest pas requise grâce à la valeur par défaut `= None`.
Le `Union` dans `Union[str, None]` permettra à votre éditeur de vous offrir un meilleur support et de détecter les erreurs.
Avoir `str | None` permettra à votre éditeur de vous offrir un meilleur support et de détecter les erreurs.
///
## Validation additionnelle
## Validation additionnelle { #additional-validation }
Nous allons imposer que bien que `q` soit un paramètre optionnel, dès qu'il est fourni, **sa longueur n'excède pas 50 caractères**.
Nous allons imposer que bien que `q` soit optionnel, dès quil est fourni, **sa longueur nexcède pas 50 caractères**.
## Importer `Query`
### Importer `Query` et `Annotated` { #import-query-and-annotated }
Pour cela, importez d'abord `Query` depuis `fastapi` :
Pour y parvenir, importez dabord :
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *}
* `Query` depuis `fastapi`
* `Annotated` depuis `typing`
## Utiliser `Query` comme valeur par défaut
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
Construisez ensuite la valeur par défaut de votre paramètre avec `Query`, en choisissant 50 comme `max_length` :
/// info
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *}
FastAPI a ajouté le support de `Annotated` (et a commencé à le recommander) dans la version 0.95.0.
Comme nous devons remplacer la valeur par défaut `None` dans la fonction par `Query()`, nous pouvons maintenant définir la valeur par défaut avec le paramètre `Query(default=None)`, il sert le même objectif qui est de définir cette valeur par défaut.
Si vous avez une version plus ancienne, vous obtiendriez des erreurs en essayant dutiliser `Annotated`.
Donc :
Vous devez vous assurer de [mettre à niveau la version de FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} au moins vers la 0.95.1 avant dutiliser `Annotated`.
///
## Utiliser `Annotated` dans le type du paramètre `q` { #use-annotated-in-the-type-for-the-q-parameter }
Vous vous souvenez que je vous ai dit précédemment que `Annotated` peut être utilisé pour ajouter des métadonnées à vos paramètres dans l[Introduction aux types Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank} ?
Cest maintenant le moment de lutiliser avec FastAPI. 🚀
Nous avions cette annotation de type :
//// tab | Python 3.10+
```Python
q: Union[str, None] = Query(default=None)
q: str | None = None
```
... rend le paramètre optionnel, et est donc équivalent à :
////
//// tab | Python 3.9+
```Python
q: Union[str, None] = None
```
Mais déclare explicitement `q` comme étant un paramètre de requête.
////
/// info
Ce que nous allons faire est denvelopper cela avec `Annotated`, pour que cela devienne :
Gardez à l'esprit que la partie la plus importante pour rendre un paramètre optionnel est :
//// tab | Python 3.10+
```Python
= None
q: Annotated[str | None] = None
```
ou :
////
//// tab | Python 3.9+
```Python
= Query(None)
q: Annotated[Union[str, None]] = None
```
et utilisera ce `None` pour détecter que ce paramètre de requête **n'est pas requis**.
////
Le `Union[str, None]` est uniquement là pour permettre à votre éditeur un meilleur support.
Ces deux versions veulent dire la même chose, `q` est un paramètre qui peut être un `str` ou `None`, et par défaut, il vaut `None`.
Passons maintenant aux choses amusantes. 🎉
## Ajouter `Query` à `Annotated` dans le paramètre `q` { #add-query-to-annotated-in-the-q-parameter }
Maintenant que nous avons ce `Annotated` où nous pouvons mettre plus dinformations (dans ce cas une validation additionnelle), ajoutez `Query` à lintérieur de `Annotated`, et définissez le paramètre `max_length` à `50` :
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
Remarquez que la valeur par défaut est toujours `None`, donc le paramètre est toujours optionnel.
Mais maintenant, avec `Query(max_length=50)` à lintérieur de `Annotated`, nous disons à FastAPI que nous voulons **de la validation additionnelle** pour cette valeur, nous voulons quelle ait au maximum 50 caractères. 😎
/// tip | Astuce
Ici, nous utilisons `Query()` parce que cest un **paramètre de requête**. Plus tard, nous verrons dautres éléments comme `Path()`, `Body()`, `Header()` et `Cookie()`, qui acceptent aussi les mêmes arguments que `Query()`.
///
Ensuite, nous pouvons passer d'autres paramètres à `Query`. Dans cet exemple, le paramètre `max_length` qui s'applique aux chaînes de caractères :
FastAPI va maintenant :
* **Valider** les données en sassurant que la longueur maximale est de 50 caractères
* Afficher une **erreur claire** pour le client quand les données ne sont pas valides
* **Documenter** le paramètre dans le schéma OpenAPI de la *path operation* (il apparaîtra donc dans l**UI de documentation automatique**)
## Alternative (ancienne) : `Query` comme valeur par défaut { #alternative-old-query-as-the-default-value }
Les versions précédentes de FastAPI (avant <abbr title="before 2023-03 - avant 2023-03">0.95.0</abbr>) vous demandaient dutiliser `Query` comme valeur par défaut de votre paramètre, au lieu de le mettre dans `Annotated`. Il y a de fortes chances que vous voyiez du code qui lutilise, donc je vais vous lexpliquer.
/// tip | Astuce
Pour du nouveau code et dès que possible, utilisez `Annotated` comme expliqué ci-dessus. Il y a plusieurs avantages (expliqués ci-dessous) et aucun inconvénient. 🍰
///
Voici comment vous utiliseriez `Query()` comme valeur par défaut du paramètre de votre fonction, en définissant le paramètre `max_length` à 50 :
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
Comme dans ce cas (sans utiliser `Annotated`) nous devons remplacer la valeur par défaut `None` dans la fonction par `Query()`, nous devons maintenant définir la valeur par défaut avec le paramètre `Query(default=None)`, il sert le même objectif de définir cette valeur par défaut (au moins pour FastAPI).
Donc :
```Python
q: Union[str, None] = Query(default=None, max_length=50)
q: str | None = Query(default=None)
```
Cela va valider les données, montrer une erreur claire si ces dernières ne sont pas valides, et documenter le paramètre dans le schéma `OpenAPI` de cette *path operation*.
... rend le paramètre optionnel, avec une valeur par défaut de `None`, identique à :
## Rajouter plus de validation
```Python
q: str | None = None
```
Vous pouvez aussi rajouter un second paramètre `min_length` :
Mais la version avec `Query` le déclare explicitement comme étant un paramètre de requête.
{* ../../docs_src/query_params_str_validations/tutorial003.py hl[9] *}
Ensuite, nous pouvons passer dautres paramètres à `Query`. Dans ce cas, le paramètre `max_length` qui sapplique aux chaînes de caractères :
## Ajouter des validations par expressions régulières
```Python
q: str | None = Query(default=None, max_length=50)
```
On peut définir une <abbr title="Une expression régulière, regex ou regexp est une suite de caractères qui définit un pattern de correspondance pour les chaînes de caractères.">expression régulière</abbr> à laquelle le paramètre doit correspondre :
Cela va valider les données, montrer une erreur claire quand les données ne sont pas valides, et documenter le paramètre dans le schéma OpenAPI de la *path operation*.
{* ../../docs_src/query_params_str_validations/tutorial004.py hl[10] *}
### `Query` comme valeur par défaut ou dans `Annotated` { #query-as-the-default-value-or-in-annotated }
Cette expression régulière vérifie que la valeur passée comme paramètre :
Gardez à lesprit que lorsque vous utilisez `Query` à lintérieur de `Annotated`, vous ne pouvez pas utiliser le paramètre `default` pour `Query`.
* `^` : commence avec les caractères qui suivent, avec aucun caractère avant ceux-là.
À la place, utilisez la valeur par défaut réelle du paramètre de la fonction. Sinon, ce serait incohérent.
Par exemple, ceci nest pas autorisé :
```Python
q: Annotated[str, Query(default="rick")] = "morty"
```
... parce quil nest pas clair si la valeur par défaut devrait être `"rick"` ou `"morty"`.
Donc, vous utiliseriez (de préférence) :
```Python
q: Annotated[str, Query()] = "rick"
```
... ou dans des bases de code plus anciennes, vous trouverez :
```Python
q: str = Query(default="rick")
```
### Avantages de `Annotated` { #advantages-of-annotated }
**Utiliser `Annotated` est recommandé** plutôt que la valeur par défaut dans les paramètres de fonction, cest **mieux** pour plusieurs raisons. 🤓
La valeur par défaut du **paramètre de la fonction** est la **vraie valeur par défaut**, cest plus intuitif avec Python en général. 😌
Vous pourriez **appeler** cette même fonction à **dautres endroits** sans FastAPI, et elle **fonctionnerait comme prévu**. Sil y a un paramètre **requis** (sans valeur par défaut), votre **éditeur** vous le signalera avec une erreur, **Python** se plaindra aussi si vous lexécutez sans passer le paramètre requis.
Quand vous nutilisez pas `Annotated` et que vous utilisez à la place le **style (ancien) avec valeur par défaut**, si vous appelez cette fonction sans FastAPI à **dautres endroits**, vous devez **vous souvenir** de passer les arguments à la fonction pour quelle fonctionne correctement, sinon les valeurs seront différentes de ce que vous attendez (par exemple `QueryInfo` ou quelque chose de similaire au lieu de `str`). Et votre éditeur ne se plaindra pas, et Python ne se plaindra pas en exécutant cette fonction, seulement quand les opérations à lintérieur échoueront.
Parce que `Annotated` peut avoir plus dune annotation de métadonnées, vous pourriez maintenant même utiliser la même fonction avec dautres outils, comme <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
## Ajouter plus de validation { #add-more-validations }
Vous pouvez aussi ajouter un paramètre `min_length` :
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
## Ajouter des expressions régulières { #add-regular-expressions }
Vous pouvez définir un `pattern` d<abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings. - Une expression régulière, regex ou regexp est une suite de caractères qui définit un motif de recherche pour les chaînes de caractères.">expression régulière</abbr> auquel le paramètre doit correspondre :
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
Ce motif dexpression régulière spécifique vérifie que la valeur de paramètre reçue :
* `^` : commence avec les caractères qui suivent, na pas de caractères avant.
* `fixedquery` : a pour valeur exacte `fixedquery`.
* `$` : se termine directement ensuite, n'a pas d'autres caractères après `fixedquery`.
* `$` : se termine , na pas dautres caractères après `fixedquery`.
Si vous vous sentez perdu avec le concept d'**expression régulière**, pas d'inquiétudes. Il s'agit d'une notion difficile pour beaucoup, et l'on peut déjà réussir à faire beaucoup sans jamais avoir à les manipuler.
Si vous vous sentez perdu avec toutes ces idées d**« regular expression »**, pas dinquiétudes. Cest un sujet difficile pour beaucoup de gens. Vous pouvez encore faire beaucoup de choses sans avoir besoin dexpressions régulières.
Mais si vous décidez d'apprendre à les utiliser, sachez qu'ensuite vous pouvez les utiliser directement dans **FastAPI**.
Maintenant, vous savez que chaque fois que vous en avez besoin, vous pouvez les utiliser dans **FastAPI**.
## Valeurs par défaut
## Valeurs par défaut { #default-values }
De la même façon que vous pouvez passer `None` comme premier argument pour l'utiliser comme valeur par défaut, vous pouvez passer d'autres valeurs.
Vous pouvez, bien sûr, utiliser des valeurs par défaut autres que `None`.
Disons que vous déclarez le paramètre `q` comme ayant une longueur minimale de `3`, et une valeur par défaut étant `"fixedquery"` :
Disons que vous voulez déclarer le paramètre de requête `q` avec un `min_length` de `3`, et avec une valeur par défaut de `"fixedquery"` :
{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *}
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
/// note | Rappel
/// note | Remarque
Avoir une valeur par défaut rend le paramètre optionnel.
Avoir une valeur par défaut de nimporte quel type, y compris `None`, rend le paramètre optionnel (non requis).
///
## Rendre ce paramètre requis
## Paramètres requis { #required-parameters }
Quand on ne déclare ni validation, ni métadonnée, on peut rendre le paramètre `q` requis en ne lui déclarant juste aucune valeur par défaut :
Quand nous navons pas besoin de déclarer plus de validations ou de métadonnées, nous pouvons rendre le paramètre de requête `q` requis simplement en ne déclarant pas de valeur par défaut, comme :
```Python
q: str
```
à la place de :
au lieu de :
```Python
q: Union[str, None] = None
q: str | None = None
```
Mais maintenant, on déclare `q` avec `Query`, comme ceci :
Mais nous le déclarons maintenant avec `Query`, par exemple comme ceci :
```Python
q: Union[str, None] = Query(default=None, min_length=3)
q: Annotated[str | None, Query(min_length=3)] = None
```
Donc pour déclarer une valeur comme requise tout en utilisant `Query`, il faut utiliser `...` comme premier argument :
Donc, lorsque vous devez déclarer une valeur comme requise tout en utilisant `Query`, vous pouvez simplement ne pas déclarer de valeur par défaut :
{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *}
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
/// info
### Requis, peut être `None` { #required-can-be-none }
Si vous n'avez jamais vu ce `...` auparavant : c'est une des constantes natives de Python <a href="https://docs.python.org/fr/3/library/constants.html#Ellipsis" class="external-link" target="_blank">appelée "Ellipsis"</a>.
Vous pouvez déclarer quun paramètre peut accepter `None`, mais quil est quand même requis. Cela forcerait les clients à envoyer une valeur, même si la valeur est `None`.
///
Pour faire cela, vous pouvez déclarer que `None` est un type valide mais simplement ne pas déclarer de valeur par défaut :
Cela indiquera à **FastAPI** que la présence de ce paramètre est obligatoire.
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
## Liste de paramètres / valeurs multiples via Query
## Liste de paramètres de requête / valeurs multiples { #query-parameter-list-multiple-values }
Quand on définit un paramètre de requête explicitement avec `Query` on peut aussi déclarer qu'il reçoit une liste de valeur, ou des "valeurs multiples".
Quand vous définissez un paramètre de requête explicitement avec `Query`, vous pouvez aussi le déclarer pour quil reçoive une liste de valeurs, ou dit autrement, pour quil reçoive plusieurs valeurs.
Par exemple, pour déclarer un paramètre de requête `q` qui peut apparaître plusieurs fois dans une URL, on écrit :
Par exemple, pour déclarer un paramètre de requête `q` qui peut apparaître plusieurs fois dans lURL, vous pouvez écrire :
{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *}
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
Ce qui fait qu'avec une URL comme :
Ensuite, avec une URL comme :
```
http://localhost:8000/items/?q=foo&q=bar
```
vous recevriez les valeurs des multiples paramètres de requête `q` (`foo` et `bar`) dans une `list` Python au sein de votre fonction de **path operation**, dans le paramètre de fonction `q`.
vous recevriez les valeurs des multiples *query parameters* `q` (`foo` et `bar`) dans une `list` Python au sein de votre *path operation function*, dans le *function parameter* `q`.
Donc la réponse de cette URL serait :
Donc la réponse à cette URL serait :
```JSON
{
@@ -173,19 +280,19 @@ Donc la réponse de cette URL serait :
/// tip | Astuce
Pour déclarer un paramètre de requête de type `list`, comme dans l'exemple ci-dessus, il faut explicitement utiliser `Query`, sinon cela sera interprété comme faisant partie du corps de la requête.
Pour déclarer un paramètre de requête avec un type `list`, comme dans lexemple ci-dessus, vous devez explicitement utiliser `Query`, sinon cela serait interprété comme un corps de la requête.
///
La documentation sera donc mise à jour automatiquement pour autoriser plusieurs valeurs :
Les documents interactifs de lAPI seront mis à jour en conséquence, pour autoriser plusieurs valeurs :
<img src="/img/tutorial/query-params-str-validations/image02.png">
### Combiner liste de paramètres et valeurs par défaut
### Liste de paramètres de requête / valeurs multiples avec des valeurs par défaut { #query-parameter-list-multiple-values-with-defaults }
Et l'on peut aussi définir une liste de valeurs par défaut si aucune n'est fournie :
Vous pouvez aussi définir une `list` de valeurs par défaut si aucune nest fournie :
{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *}
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
Si vous allez à :
@@ -193,9 +300,7 @@ Si vous allez à :
http://localhost:8000/items/
```
la valeur par défaut de `q` sera : `["foo", "bar"]`
et la réponse sera :
la valeur par défaut de `q` sera : `["foo", "bar"]` et votre réponse sera :
```JSON
{
@@ -206,93 +311,163 @@ et la réponse sera :
}
```
#### Utiliser `list`
#### Utiliser seulement `list` { #using-just-list }
Il est aussi possible d'utiliser directement `list` plutôt que `List[str]` :
Vous pouvez aussi utiliser `list` directement au lieu 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
/// note | Remarque
Dans ce cas-là, **FastAPI** ne vérifiera pas le contenu de la liste.
Gardez à lesprit que dans ce cas, FastAPI ne vérifiera pas le contenu de la liste.
Par exemple, `List[int]` vérifiera (et documentera) que la liste est bien entièrement composée d'entiers. Alors qu'un simple `list` ne ferait pas cette vérification.
Par exemple, `list[int]` vérifierait (et documenterait) que le contenu de la liste est composé dentiers. Mais `list` seul ne le ferait pas.
///
## Déclarer des métadonnées supplémentaires
## Déclarer plus de métadonnées { #declare-more-metadata }
On peut aussi ajouter plus d'informations sur le paramètre.
Vous pouvez ajouter plus dinformations à propos du paramètre.
Ces informations seront incluses dans le schéma `OpenAPI` généré et utilisées par la documentation interactive ou les outils externes utilisés.
Ces informations seront incluses dans lOpenAPI généré et utilisées par les interfaces utilisateur de la documentation et les outils externes.
/// note
/// note | Remarque
Gardez en tête que les outils externes utilisés ne supportent pas forcément tous parfaitement OpenAPI.
Gardez à lesprit que différents outils peuvent avoir des niveaux de support dOpenAPI différents.
Il se peut donc que certains d'entre eux n'utilisent pas toutes les métadonnées que vous avez déclarées pour le moment, bien que dans la plupart des cas, les fonctionnalités manquantes ont prévu d'être implémentées.
Certains dentre eux pourraient ne pas montrer toutes les informations supplémentaires déclarées pour le moment, bien que dans la plupart des cas, la fonctionnalité manquante soit déjà prévue pour le développement.
///
Vous pouvez ajouter un `title` :
{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *}
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
Et une `description` :
{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *}
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
## Alias de paramètres
## Alias de paramètres { #alias-parameters }
Imaginez que vous vouliez que votre paramètre se nomme `item-query`.
Imaginez que vous voulez que le paramètre soit `item-query`.
Comme dans la requête :
Comme dans :
```
http://127.0.0.1:8000/items/?item-query=foobaritems
```
Mais `item-query` n'est pas un nom de variable valide en Python.
Mais `item-query` nest pas un nom de variable Python valide.
Le nom le plus proche serait `item_query`.
Le plus proche serait `item_query`.
Mais vous avez vraiment envie que ce soit exactement `item-query`...
Mais vous en avez encore besoin pour quil soit exactement `item-query`...
Pour cela vous pouvez déclarer un `alias`, et cet alias est ce qui sera utilisé pour trouver la valeur du paramètre :
Alors vous pouvez déclarer un `alias`, et cet alias sera ce qui sera utilisé pour trouver la valeur du paramètre :
{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *}
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
## Déprécier des paramètres
## Déprécier des paramètres { #deprecating-parameters }
Disons que vous ne vouliez plus utiliser ce paramètre désormais.
Disons maintenant que vous naimez plus ce paramètre.
Il faut qu'il continue à exister pendant un certain temps car vos clients l'utilisent, mais vous voulez que la documentation mentionne clairement que ce paramètre est <abbr title="obsolète, recommandé de ne pas l'utiliser">déprécié</abbr>.
Vous devez le laisser pendant un moment parce quil y a des clients qui lutilisent, mais vous voulez que les documents le montrent clairement comme <abbr title="obsolete, recommended not to use it - obsolète, recommandé de ne pas lutiliser">deprecated</abbr>.
On utilise alors l'argument `deprecated=True` de `Query` :
Ensuite, passez le paramètre `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] *}
La documentation le présentera comme il suit :
Les documents lafficheront comme ceci :
<img src="/img/tutorial/query-params-str-validations/image01.png">
## Pour résumer
## Exclusion d'OpenAPI { #exclude-parameters-from-openapi }
Il est possible d'ajouter des validateurs et métadonnées pour vos paramètres.
Pour exclure un paramètre de requête du schéma OpenAPI généré (et donc, des systèmes de documentation automatique), définissez le paramètre `include_in_schema` de `Query` à `False` :
Validateurs et métadonnées génériques:
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
## Validation personnalisée { #custom-validation }
Il peut y avoir des cas où vous devez faire de la **validation personnalisée** qui ne peut pas être faite avec les paramètres montrés ci-dessus.
Dans ces cas, vous pouvez utiliser une **fonction de validateur personnalisée** qui est appliquée après la validation normale (par ex. après avoir validé que la valeur est un `str`).
Vous pouvez y parvenir en utilisant <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">`AfterValidator` de Pydantic</a> à lintérieur de `Annotated`.
/// tip | Astuce
Pydantic a aussi <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator" class="external-link" target="_blank">`BeforeValidator`</a> et dautres. 🤓
///
Par exemple, ce validateur personnalisé vérifie que lID de litem commence par `isbn-` pour un numéro de livre <abbr title="ISBN means International Standard Book Number - ISBN signifie International Standard Book Number">ISBN</abbr> ou par `imdb-` pour un ID dURL de film <abbr title="IMDB (Internet Movie Database) is a website with information about movies - IMDB (Internet Movie Database) est un site web contenant des informations sur les films">IMDB</abbr> :
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
/// info
Ceci est disponible avec Pydantic version 2 ou supérieure. 😎
///
/// tip | Astuce
Si vous devez faire tout type de validation qui nécessite de communiquer avec un **composant externe**, comme une base de données ou une autre API, vous devriez plutôt utiliser les **dépendances FastAPI**, vous en apprendrez plus à leur sujet plus tard.
Ces validateurs personnalisés sont pour des choses qui peuvent être vérifiées avec **uniquement** les **mêmes données** fournies dans la requête.
///
### Comprendre ce code { #understand-that-code }
Le point important est simplement dutiliser **`AfterValidator` avec une fonction dans `Annotated`**. Nhésitez pas à sauter cette partie. 🤸
---
Mais si vous êtes curieux à propos de cet exemple de code spécifique et que vous êtes encore diverti, voici quelques détails supplémentaires.
#### Chaîne avec `value.startswith()` { #string-with-value-startswith }
Avez-vous remarqué ? une chaîne utilisant `value.startswith()` peut prendre un tuple, et cela vérifiera chaque valeur dans le tuple :
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
#### Un item aléatoire { #a-random-item }
Avec `data.items()` nous obtenons un <abbr title="Something we can iterate on with a for loop, like a list, set, etc. - Quelque chose sur lequel on peut itérer avec une boucle for, comme une liste, un set, etc.">iterable object</abbr> avec des tuples contenant la clé et la valeur pour chaque élément du dictionnaire.
Nous convertissons cet objet itérable en une vraie `list` avec `list(data.items())`.
Ensuite, avec `random.choice()` nous pouvons obtenir une **valeur aléatoire** de la liste, donc, nous obtenons un tuple avec `(id, name)`. Ce sera quelque chose comme `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
Ensuite, nous **assignons ces deux valeurs** du tuple aux variables `id` et `name`.
Ainsi, si lutilisateur na pas fourni dID ditem, il recevra quand même une suggestion aléatoire.
... nous faisons tout cela en **une seule ligne simple**. 🤯 Vous naimez pas Python ? 🐍
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
## Pour résumer { #recap }
Vous pouvez déclarer des validations additionnelles et des métadonnées pour vos paramètres.
Validations et métadonnées génériques :
* `alias`
* `title`
* `description`
* `deprecated`
Validateurs spécifiques aux chaînes de caractères :
Validations spécifiques pour les chaînes de caractères :
* `min_length`
* `max_length`
* `regex`
* `pattern`
Parmi ces exemples, vous avez pu voir comment déclarer des validateurs pour les chaînes de caractères.
Validations personnalisées utilisant `AfterValidator`.
Dans les prochains chapitres, vous verrez comment déclarer des validateurs pour d'autres types, comme les nombres.
Dans ces exemples, vous avez vu comment déclarer des validations pour des valeurs `str`.
Consultez les chapitres suivants pour apprendre à déclarer des validations pour dautres types, comme les nombres.

View File

@@ -1,10 +1,10 @@
# Paramètres de requête
# Paramètres de requête { #query-parameters }
Quand vous déclarez des paramètres dans votre fonction de chemin qui ne font pas partie des paramètres indiqués dans le chemin associé, ces paramètres sont automatiquement considérés comme des paramètres de "requête".
Quand vous déclarez d'autres paramètres de fonction qui ne font pas partie des paramètres de chemin, ils sont automatiquement interprétés comme des paramètres de « requête ».
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
La partie appelée requête (ou **query**) dans une URL est l'ensemble des paires clés-valeurs placées après le `?` , séparées par des `&`.
La requête est l'ensemble des paires clé-valeur qui suivent le `?` dans une URL, séparées par des caractères `&`.
Par exemple, dans l'URL :
@@ -12,80 +12,72 @@ Par exemple, dans l'URL :
http://127.0.0.1:8000/items/?skip=0&limit=10
```
...les paramètres de requête sont :
... les paramètres de requête sont :
* `skip` : avec une valeur de`0`
* `skip` : avec une valeur de `0`
* `limit` : avec une valeur de `10`
Faisant partie de l'URL, ces valeurs sont des chaînes de caractères (`str`).
Comme ils font partie de l'URL, ce sont « naturellement » des chaînes de caractères.
Mais quand on les déclare avec des types Python (dans l'exemple précédent, en tant qu'`int`), elles sont converties dans les types renseignés.
Mais lorsque vous les déclarez avec des types Python (dans l'exemple ci-dessus, en tant que `int`), ils sont convertis vers ce type et validés par rapport à celui-ci.
Toutes les fonctionnalités qui s'appliquent aux paramètres de chemin s'appliquent aussi aux paramètres de requête :
Tous les mêmes processus qui s'appliquaient aux paramètres de chemin s'appliquent aussi aux paramètres de requête :
* Support de l'éditeur : vérification d'erreurs, auto-complétion, etc.
* <abbr title="conversion de la chaîne de caractères venant de la requête HTTP en données Python">"Parsing"</abbr> de données.
* Validation de données.
* Annotations d'API et documentation automatique.
* Support de l'éditeur (évidemment)
* <abbr title="converting the string that comes from an HTTP request into Python data - conversion de la chaîne de caractères venant d'une requête HTTP en données Python">« parsing »</abbr> des données
* Validation des données
* Documentation automatique
## Valeurs par défaut
## Valeurs par défaut { #defaults }
Les paramètres de requête ne sont pas une partie fixe d'un chemin, ils peuvent être optionnels et avoir des valeurs par défaut.
Comme les paramètres de requête ne constituent pas une partie fixe d'un chemin, ils peuvent être optionnels et peuvent avoir des valeurs par défaut.
Dans l'exemple ci-dessus, ils ont des valeurs par défaut qui sont `skip=0` et `limit=10`.
Dans l'exemple ci-dessus, ils ont des valeurs par défaut `skip=0` et `limit=10`.
Donc, accéder à l'URL :
Ainsi, aller à l'URL :
```
http://127.0.0.1:8000/items/
```
serait équivalent à accéder à l'URL :
serait équivalent à aller à :
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
Mais si vous accédez à, par exemple :
Mais si vous allez à, par exemple :
```
http://127.0.0.1:8000/items/?skip=20
```
Les valeurs des paramètres de votre fonction seront :
Les valeurs des paramètres dans votre fonction seront :
* `skip=20` : car c'est la valeur déclarée dans l'URL.
* `limit=10` : car `limit` n'a pas été déclaré dans l'URL, et que la valeur par défaut était `10`.
* `skip=20` : parce que vous l'avez défini dans l'URL
* `limit=10` : parce que c'était la valeur par défaut
## Paramètres optionnels
## Paramètres optionnels { #optional-parameters }
De la même façon, vous pouvez définir des paramètres de requête comme optionnels, en leur donnant comme valeur par défaut `None` :
De la même façon, vous pouvez déclarer des paramètres de requête optionnels, en définissant leur valeur par défaut à `None` :
{* ../../docs_src/query_params/tutorial002.py hl[9] *}
{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *}
Ici, le paramètre `q` sera optionnel, et aura `None` comme valeur par défaut.
Dans ce cas, le paramètre de fonction `q` sera optionnel, et aura `None` comme valeur par défaut.
/// check | Remarque
/// check | Vérifications
On peut voir que **FastAPI** est capable de détecter que le paramètre de chemin `item_id` est un paramètre de chemin et que `q` n'en est pas un, c'est donc un paramètre de requête.
Notez également que **FastAPI** est suffisamment intelligent pour remarquer que le paramètre de chemin `item_id` est un paramètre de chemin et que `q` n'en est pas un, donc c'est un paramètre de requête.
///
/// note
## Conversion du type des paramètres de requête { #query-parameter-type-conversion }
**FastAPI** saura que `q` est optionnel grâce au `=None`.
Vous pouvez aussi déclarer des types `bool`, et ils seront convertis :
Le `Optional` dans `Optional[str]` n'est pas utilisé par **FastAPI** (**FastAPI** n'en utilisera que la partie `str`), mais il servira tout de même à votre éditeur de texte pour détecter des erreurs dans votre code.
{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *}
///
## Conversion des types des paramètres de requête
Vous pouvez aussi déclarer des paramètres de requête comme booléens (`bool`), **FastAPI** les convertira :
{* ../../docs_src/query_params/tutorial003.py hl[9] *}
Avec ce code, en allant sur :
Dans ce cas, si vous allez à :
```
http://127.0.0.1:8000/items/foo?short=1
@@ -115,60 +107,62 @@ ou
http://127.0.0.1:8000/items/foo?short=yes
```
ou n'importe quelle autre variation de casse (tout en majuscules, uniquement la première lettre en majuscule, etc.), votre fonction considérera le paramètre `short` comme ayant une valeur booléenne à `True`. Sinon la valeur sera à `False`.
ou toute autre variation de casse (majuscules, première lettre en majuscule, etc.), votre fonction verra le paramètre `short` avec une valeur `bool` de `True`. Sinon, `False`.
## Multiples paramètres de chemin et de requête
Vous pouvez déclarer plusieurs paramètres de chemin et paramètres de requête dans la même fonction, **FastAPI** saura comment les gérer.
## Paramètres de chemin et de requête multiples { #multiple-path-and-query-parameters }
Vous pouvez déclarer plusieurs paramètres de chemin et paramètres de requête en même temps, **FastAPI** sait lesquels sont lesquels.
Et vous n'avez pas besoin de les déclarer dans un ordre spécifique.
Ils seront détectés par leurs noms :
Ils seront détectés par leur nom :
{* ../../docs_src/query_params/tutorial004.py hl[8,10] *}
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
## Paramètres de requête requis
## Paramètres de requête requis { #required-query-parameters }
Quand vous déclarez une valeur par défaut pour un paramètre qui n'est pas un paramètre de chemin (actuellement, nous n'avons vu que les paramètres de requête), alors ce paramètre n'est pas requis.
Lorsque vous déclarez une valeur par défaut pour des paramètres qui ne sont pas des paramètres de chemin (pour l'instant, nous n'avons vu que des paramètres de requête), alors ils ne sont pas requis.
Si vous ne voulez pas leur donner de valeur par défaut mais juste les rendre optionnels, utilisez `None` comme valeur par défaut.
Si vous ne voulez pas ajouter une valeur spécifique mais juste le rendre optionnel, définissez la valeur par défaut sur `None`.
Mais si vous voulez rendre un paramètre de requête obligatoire, vous pouvez juste ne pas y affecter de valeur par défaut :
Mais lorsque vous voulez rendre un paramètre de requête requis, vous pouvez simplement ne pas déclarer de valeur par défaut :
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
Ici le paramètre `needy` est un paramètre requis (ou obligatoire) de type `str`.
Ici, le paramètre de requête `needy` est un paramètre de requête requis de type `str`.
Si vous ouvrez une URL comme :
Si vous ouvrez dans votre navigateur une URL comme :
```
http://127.0.0.1:8000/items/foo-item
```
...sans ajouter le paramètre requis `needy`, vous aurez une erreur :
... sans ajouter le paramètre requis `needy`, vous verrez une erreur comme :
```JSON
{
"detail": [
{
"loc": [
"query",
"needy"
],
"msg": "field required",
"type": "value_error.missing"
}
]
"detail": [
{
"type": "missing",
"loc": [
"query",
"needy"
],
"msg": "Field required",
"input": null
}
]
}
```
La présence de `needy` étant nécessaire, vous auriez besoin de l'insérer dans l'URL :
Comme `needy` est un paramètre requis, vous devez le définir dans l'URL :
```
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
```
...ce qui fonctionnerait :
... cela fonctionnerait :
```JSON
{
@@ -177,18 +171,18 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
}
```
Et bien sur, vous pouvez définir certains paramètres comme requis, certains avec des valeurs par défaut et certains entièrement optionnels :
Et bien sûr, vous pouvez définir certains paramètres comme requis, certains avec une valeur par défaut et certains entièrement optionnels :
{* ../../docs_src/query_params/tutorial006.py hl[10] *}
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
Ici, on a donc 3 paramètres de requête :
Dans ce cas, il y a 3 paramètres de requête :
* `needy`, requis et de type `str`.
* `skip`, un `int` avec comme valeur par défaut `0`.
* `needy`, un `str` requis.
* `skip`, un `int` avec une valeur par défaut de `0`.
* `limit`, un `int` optionnel.
/// tip | Astuce
Vous pouvez utiliser les `Enum`s de la même façon qu'avec les [Paramètres de chemin](path-params.md#valeurs-predefinies){.internal-link target=_blank}.
Vous pouvez aussi utiliser des `Enum`s de la même façon qu'avec les [Paramètres de chemin](path-params.md#predefined-values){.internal-link target=_blank}.
///

View File

@@ -6,123 +6,127 @@ Language code: fr.
### Grammar to use when talking to the reader
Use the formal grammar (use «vous» instead of «tu»).
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
1) Convert neutral double quotes («"») and English double typographic quotes («“» and «”») to French guillemets (««» and «»»).
- Convert neutral double quotes (`"`) to French guillemets (`«` and `»`).
2) In the French docs, guillemets are written without extra spaces: use «texte», not « texte ».
3) Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks.
- Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks.
Examples:
Source (English):
Source (English):
«««
"Hello world"
“Hello Universe”
"He said: 'Hello'"
"The module is `__main__`"
»»»
```
"Hello world"
“Hello Universe”
"He said: 'Hello'"
"The module is `__main__`"
```
Result (French):
Result (French):
«««
«Hello world»
«Hello Universe»
«He said: 'Hello'»
«The module is `__main__`»
»»»
```
"Hello world"
Hello Universe
"He said: 'Hello'"
"The module is `__main__`"
```
### Ellipsis
1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis.
- Make sure there is a space between an ellipsis and a word following or preceding the ellipsis.
Examples:
Source (English):
Source (English):
«««
...as we intended.
...this would work:
...etc.
others...
More to come...
»»»
```
...as we intended.
...this would work:
...etc.
others...
More to come...
```
Result (French):
Result (French):
«««
... comme prévu.
... cela fonctionnerait :
... etc.
D'autres ...
La suite ...
»»»
```
... comme prévu.
... cela fonctionnerait :
... etc.
D'autres ...
La suite ...
```
2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
- This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
### Headings
1) Prefer translating headings using the infinitive form (as is common in the existing French docs): «Créer…», «Utiliser…», «Ajouter…».
- Prefer translating headings using the infinitive form (as is common in the existing French docs): `Créer…`, `Utiliser…`, `Ajouter…`.
2) For headings that are instructions written in imperative in English (e.g. Go check …), keep them in imperative in French, using the formal grammar (e.g. «Allez voir …»).
3) Keep heading punctuation as in the source. In particular, keep occurrences of literal « - » (space-hyphen-space) as « - » (the existing French docs use a hyphen here).
- 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 when that is the established usage in the French docs (e.g. «framework», «endpoint», «plug-in», «payload»). Use French where the existing docs already consistently use French (e.g. «requête», «réponse»).
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.
Below is a list of English terms and their preferred French translations, separated by a colon (:). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them.
* «/// note | Technical Details»: «/// note | Détails techniques»
* «/// note»: «/// note | Remarque»
* «/// tip»: «/// tip | Astuce»
* «/// warning»: «/// warning | Attention»
* «/// check»: «/// check | vérifier»
* «/// info»: «/// info»
- /// 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»
- the docs: les documents
- the documentation: la documentation
* «framework»: «framework» (do not translate to «cadre»)
* «performance»: «performance»
- Exclude from OpenAPI: Exclusion d'OpenAPI
* «type hints»: «annotations de type»
* «type annotations»: «annotations de type»
- framework: framework (do not translate to cadre)
- performance: performance
* «autocomplete»: «autocomplétion»
* «autocompletion»: «autocomplétion»
- type hints: annotations de type
- type annotations: annotations de type
* «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»
- autocomplete: autocomplétion
- autocompletion: autocomplétion
* «the request body»: «le corps de la requête»
* «the response body»: «le corps de la réponse»
- 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
* «path operation»: «opération de chemin»
* «path operations» (plural): «opérations de chemin»
* «path operation function»: «fonction de chemin»
* «path operation decorator»: «décorateur d'opération de chemin»
- the request body: le corps de la requête
- the response body: le corps de la réponse
* «path parameter»: «paramètre de chemin»
* «query parameter»: «paramètre de requête»
- 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
* «the `Request`»: «`Request`» (keep as code identifier)
* «the `Response`»: «`Response`» (keep as code identifier)
- path parameter: paramètre de chemin
- query parameter: paramètre de requête
* «deployment»: «déploiement»
* «to upgrade»: «mettre à niveau»
- the `Request`: `Request` (keep as code identifier)
- the `Response`: `Response` (keep as code identifier)
* «deprecated»: «déprécié»
* «to deprecate»: «déprécier»
- deployment: déploiement
- to upgrade: mettre à niveau
* «cheat sheet»: «aide-mémoire»
* «plug-in»: «plug-in»
- deprecated: déprécié
- to deprecate: déprécier
- cheat sheet: aide-mémoire
- plug-in: plug-in

503
docs/ko/docs/_llm-test.md Normal file
View File

@@ -0,0 +1,503 @@
# LLM 테스트 파일 { #llm-test-file }
이 문서는 문서를 번역하는 <abbr title="Large Language Model - 대규모 언어 모델">LLM</abbr>이 `scripts/translate.py``general_prompt``docs/{language code}/llm-prompt.md`의 언어별 프롬프트를 이해하는지 테스트합니다. 언어별 프롬프트는 `general_prompt`에 추가됩니다.
여기에 추가된 테스트는 언어별 프롬프트를 설계하는 모든 사람이 보게 됩니다.
사용 방법은 다음과 같습니다:
* 언어별 프롬프트 `docs/{language code}/llm-prompt.md`를 준비합니다.
* 이 문서를 원하는 대상 언어로 새로 번역합니다(예: `translate.py``translate-page` 명령). 그러면 `docs/{language code}/docs/_llm-test.md` 아래에 번역이 생성됩니다.
* 번역에서 문제가 없는지 확인합니다.
* 필요하다면 언어별 프롬프트, 일반 프롬프트, 또는 영어 문서를 개선합니다.
* 그런 다음 번역에서 남아 있는 문제를 수동으로 수정해 좋은 번역이 되게 합니다.
* 좋은 번역을 둔 상태에서 다시 번역합니다. 이상적인 결과는 LLM이 더 이상 번역에 변경을 만들지 않는 것입니다. 이는 일반 프롬프트와 언어별 프롬프트가 가능한 한 최선이라는 뜻입니다(때때로 몇 가지 seemingly random 변경을 할 수 있는데, 그 이유는 <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM은 결정론적 알고리즘이 아니기 때문</a>입니다).
테스트:
## 코드 스니펫 { #code-snippets }
//// tab | 테스트
다음은 코드 스니펫입니다: `foo`. 그리고 이것은 또 다른 코드 스니펫입니다: `bar`. 그리고 또 하나: `baz quux`.
////
//// tab | 정보
코드 스니펫의 내용은 그대로 두어야 합니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Content of code snippets` 섹션을 참고하세요.
////
## 따옴표 { #quotes }
//// tab | 테스트
어제 제 친구가 이렇게 썼습니다: "If you spell incorrectly correctly, you have spelled it incorrectly". 이에 저는 이렇게 답했습니다: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"".
/// note | 참고
LLM은 아마 이것을 잘못 번역할 것입니다. 흥미로운 점은 재번역할 때 고정된 번역을 유지하는지 여부뿐입니다.
///
////
//// tab | 정보
프롬프트 설계자는 중립 따옴표를 타이포그래피 따옴표로 변환할지 선택할 수 있습니다. 그대로 두어도 괜찮습니다.
예를 들어 `docs/de/llm-prompt.md``### Quotes` 섹션을 참고하세요.
////
## 코드 스니펫의 따옴표 { #quotes-in-code-snippets }
//// tab | 테스트
`pip install "foo[bar]"`
코드 스니펫에서 문자열 리터럴의 예: `"this"`, `'that'`.
코드 스니펫에서 문자열 리터럴의 어려운 예: `f"I like {'oranges' if orange else "apples"}"`
하드코어: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"`
////
//// tab | 정보
... 하지만 코드 스니펫 안의 따옴표는 그대로 유지되어야 합니다.
////
## 코드 블록 { #code-blocks }
//// tab | 테스트
Bash 코드 예시...
```bash
# 우주에 인사말 출력
echo "Hello universe"
```
...그리고 콘솔 코드 예시...
```console
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting server
Searching for package file structure
```
...그리고 또 다른 콘솔 코드 예시...
```console
// "Code" 디렉터리 생성
$ mkdir code
// 해당 디렉터리로 이동
$ cd code
```
...그리고 Python 코드 예시...
```Python
wont_work() # 이건 동작하지 않습니다 😱
works(foo="bar") # 이건 동작합니다 🎉
```
...이상입니다.
////
//// tab | 정보
코드 블록의 코드는(주석을 제외하고) 수정하면 안 됩니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Content of code blocks` 섹션을 참고하세요.
////
## 탭과 색상 박스 { #tabs-and-colored-boxes }
//// tab | 테스트
/// info | 정보
일부 텍스트
///
/// note | 참고
일부 텍스트
///
/// note Technical details | 기술 세부사항
일부 텍스트
///
/// check | 확인
일부 텍스트
///
/// tip | 팁
일부 텍스트
///
/// warning | 경고
일부 텍스트
///
/// danger | 위험
일부 텍스트
///
////
//// tab | 정보
탭과 `Info`/`Note`/`Warning`/등의 블록은 제목 번역을 수직 막대(`|`) 뒤에 추가해야 합니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Special blocks``### Tab blocks` 섹션을 참고하세요.
////
## 웹 및 내부 링크 { #web-and-internal-links }
//// tab | 테스트
링크 텍스트는 번역되어야 하고, 링크 주소는 변경되지 않아야 합니다:
* [위의 제목으로 가는 링크](#code-snippets)
* [내부 링크](index.md#installation){.internal-link target=_blank}
* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">외부 링크</a>
* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">스타일로 가는 링크</a>
* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">스크립트로 가는 링크</a>
* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">이미지로 가는 링크</a>
링크 텍스트는 번역되어야 하고, 링크 주소는 번역 페이지를 가리켜야 합니다:
* <a href="https://fastapi.tiangolo.com/ko/" class="external-link" target="_blank">FastAPI 링크</a>
////
//// tab | 정보
링크는 번역되어야 하지만, 주소는 변경되지 않아야 합니다. 예외는 FastAPI 문서 페이지로 향하는 절대 링크이며, 이 경우 번역 페이지로 연결되어야 합니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Links` 섹션을 참고하세요.
////
## HTML "abbr" 요소 { #html-abbr-elements }
//// tab | 테스트
여기 HTML "abbr" 요소로 감싼 몇 가지가 있습니다(일부는 임의로 만든 것입니다):
### abbr가 전체 문구를 제공 { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done - 일을 끝내는 방법론">GTD</abbr>
* <abbr title="less than - 보다 작음"><code>lt</code></abbr>
* <abbr title="XML Web Token - XML 웹 토큰">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - 병렬 서버 게이트웨이 인터페이스">PSGI</abbr>
### abbr가 설명을 제공 { #the-abbr-gives-an-explanation }
* <abbr title="어떤 방식으로든 서로 연결되고 함께 작동하도록 구성된 머신들의 집합입니다.">cluster</abbr>
* <abbr title="입력과 출력 계층 사이에 수많은 은닉 계층을 둔 인공 신경망을 사용하는 머신 러닝 방법으로, 이를 통해 포괄적인 내부 구조를 형성합니다">Deep Learning</abbr>
### abbr가 전체 문구와 설명을 제공 { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network - 모질라 개발자 네트워크: Firefox를 만드는 사람들이 작성한 개발자용 문서">MDN</abbr>
* <abbr title="Input/Output - 입력/출력: 디스크 읽기 또는 쓰기, 네트워크 통신.">I/O</abbr>.
////
//// 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` 섹션을 참고하세요.
////

View File

@@ -1,3 +1,3 @@
# 소개
# 소개 { #about }
FastAPI에 대한 디자인, 영감 등에 대해 🤓
FastAPI, 그 디자인, 영감 등에 대해 🤓

View File

@@ -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 문서에 표시됩니다:
<img src="/img/tutorial/additional-responses/image01.png">
## 미리 정의된 응답과 커스텀 응답 결합하기 { #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 사양의 다음 섹션을 확인하세요:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI Responses Object</a>: `Response Object`를 포함합니다.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI Response Object</a>: `responses` 파라미터 안의 각 응답에 이것의 어떤 항목이든 직접 포함할 수 있습니다. `description`, `headers`, `content`(여기에서 서로 다른 미디어 타입과 JSON Schema를 선언합니다), `links` 등을 포함할 수 있습니다.

View File

@@ -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 문서)에 포함되지 않습니다.

View File

@@ -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`가 있는 의존성에 대한 조기 종료가 어떤 점에서 이득이 되는지를 포함해 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussion Question</a>을 생성해 주세요.
`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를 전달한 다음 백그라운드 태스크 함수 내부에서 객체를 다시 가져오면 됩니다.

View File

@@ -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`<a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>를 기반으로 하며, 다행히 HTTPX를 직접 사용해 API를 테스트할 수 습니다.
`TestClient`는 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>를 기반으로 하고 있으며, 다행히 이를 직접 사용하여 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 }
아래의 명령어로 테스트 코드를 실행니다:
다음과 같이 평소처럼 테스트를 실행할 수 있습니다:
<div class="termy">
@@ -57,52 +52,48 @@ $ pytest
</div>
## 자세히 보기
## 자세히 보기 { #in-detail }
`@pytest.mark.anyio` 마커는 pytest에게 이 테스트 함수가 비동기로 호출되어야 함을 알려줍니다:
`@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 | 팁
테스트 함수가 이제 `TestClient`를 사용할 때처럼 단순히 `def`가 아니라 `async def`로 작성된 점에 주목해주세요.
`TestClient`를 사용할 때처럼 단순히 `def`가 아니라, 이제 테스트 함수가 `async def`라는 점에 주목세요.
///
그 다음 `AsyncClient` 로 앱을 만들고 비동기 요청을 `await` 키워드로 보낼 수 있습니다:
그 다음 앱으로 `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] *}
위의 코드는:
이는 다음과 동등합니다:
```Python
response = client.get('/')
```
`TestClient` 요청을 보내던 것과 동일합니다.
`TestClient` 요청을 보내기 위해 사용하던 코드입니다.
/// tip | 팁
로운 `AsyncClient`를 사용할 때 async/await를 사용하고 있다는 점에 주목하세요. 요청은 비동기적으로 처리됩니다.
`AsyncClient`와 함께 async/await를 사용하고 있다는 점에 주목하세요. 요청은 비동기니다.
///
/// warning | 경고
만약의 어플리케이션이 Lifespan 이벤트에 의존성을 갖고 있다면 `AsyncClient` 이러한 이벤트를 실행시키지 않습니다.
`AsyncClient` 가 테스트를 실행시켰다는 것을 확인하기 위해
`LifespanManager` from <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.확인해주세요.
플리케이션이 lifespan 이벤트에 의존다면, `AsyncClient` 이러한 이벤트를 트리거하지 않습니다. 이벤트가 트리거되도록 하려면 <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>의 `LifespanManager`를 사용하세요.
///
## 그 외의 비동기 함수 호출
## 기타 비동기 함수 호출 { #other-asynchronous-function-calls }
테스트 함수가 이제 비동기 함수이므로, FastAPI 애플리케이션에 요청을 보내는 것 외에도 다른 `async` 함수를 호출하고 `await` 키워드를 사용 할 수 있습니다.
테스트 함수가 이제 비동기이므로, 테스트에서 FastAPI 애플리케이션에 요청을 보내는 것 외에도 다른 `async` 함수를 코드의 다른 곳에서 호출하듯이 동일하게 호출하고 (`await`) 사용할 수 있습니다.
/// tip | 팁
테스트에 비동기 함수 호출을 통합할 때 (예: <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB의 MotorClient</a>를 사용할 때) `RuntimeError: Task attached to a different loop` 오류가 발생한다면, 이벤트 루프가 필요한 객체는 반드시 비동기 함수 에서만 인스턴스화해야 한다는 점을 주의하세요!
예를 들어 `@app.on_event("startup")` 콜백 내에서 인스턴스화하는 것이 좋습니다.
테스트에 비동기 함수 호출을 통합할 때(예: <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB의 MotorClient</a>를 사용할 때) `RuntimeError: Task attached to a different loop`를 마주친다면, 이벤트 루프가 필요한 객체는 async 함수 에서만 인스턴스화해야 한다는 점을 기억하세요. 예를 들어 `@app.on_event("startup")` 콜백에서 인스턴스화할 수 있습니다.
///

View File

@@ -0,0 +1,466 @@
# 프록시 뒤에서 실행하기 { #behind-a-proxy }
많은 경우 FastAPI 앱 앞단에 Traefik이나 Nginx 같은 **프록시(proxy)**를 두고 사용합니다.
이런 프록시는 HTTPS 인증서 처리 등 여러 작업을 담당할 수 있습니다.
## 프록시 전달 헤더 { #proxy-forwarded-headers }
애플리케이션 앞단의 **프록시**는 보통 **서버**로 요청을 보내기 전에, 해당 요청이 프록시에 의해 **전달(forwarded)**되었다는 것을 서버가 알 수 있도록 몇몇 헤더를 동적으로 설정합니다. 이를 통해 서버는 도메인을 포함한 원래의 (공개) URL, HTTPS 사용 여부 등 정보를 알 수 있습니다.
**서버** 프로그램(예: **FastAPI CLI**를 통해 실행되는 **Uvicorn**)은 이런 헤더를 해석할 수 있고, 그 정보를 애플리케이션으로 전달할 수 있습니다.
하지만 보안상, 서버는 자신이 신뢰할 수 있는 프록시 뒤에 있다는 것을 모르면 해당 헤더를 해석하지 않습니다.
/// note | 기술 세부사항
프록시 헤더는 다음과 같습니다:
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
///
### 프록시 전달 헤더 활성화하기 { #enable-proxy-forwarded-headers }
FastAPI CLI를 *CLI 옵션* `--forwarded-allow-ips`로 실행하고, 전달 헤더를 읽을 수 있도록 신뢰할 IP 주소들을 넘길 수 있습니다.
`--forwarded-allow-ips="*"`로 설정하면 들어오는 모든 IP를 신뢰합니다.
**서버**가 신뢰할 수 있는 **프록시** 뒤에 있고 프록시만 서버에 접근한다면, 이는 해당 **프록시**의 IP가 무엇이든 간에 받아들이게 됩니다.
<div class="termy">
```console
$ fastapi run --forwarded-allow-ips="*"
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### HTTPS에서 리디렉션 { #redirects-with-https }
예를 들어, *경로 처리* `/items/`를 정의했다고 해봅시다:
{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *}
클라이언트가 `/items`로 접근하면, 기본적으로 `/items/`로 리디렉션됩니다.
하지만 *CLI 옵션* `--forwarded-allow-ips`를 설정하기 전에는 `http://localhost:8000/items/`로 리디렉션될 수 있습니다.
그런데 애플리케이션이 `https://mysuperapp.com`에 호스팅되어 있고, 리디렉션도 `https://mysuperapp.com/items/`로 되어야 할 수 있습니다.
이때 `--proxy-headers`를 설정하면 FastAPI가 올바른 위치로 리디렉션할 수 있습니다. 😎
```
https://mysuperapp.com/items/
```
/// tip | 팁
HTTPS에 대해 더 알아보려면 가이드 [HTTPS에 대하여](../deployment/https.md){.internal-link target=_blank}를 확인하세요.
///
### 프록시 전달 헤더가 동작하는 방식 { #how-proxy-forwarded-headers-work }
다음은 **프록시**가 클라이언트와 **애플리케이션 서버** 사이에서 전달 헤더를 추가하는 과정을 시각적으로 나타낸 것입니다:
```mermaid
sequenceDiagram
participant Client
participant Proxy as Proxy/Load Balancer
participant Server as FastAPI Server
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
Note over Proxy: Proxy adds forwarded headers
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
Proxy->>Client: HTTPS Response
```
**프록시**는 원래의 클라이언트 요청을 가로채고, **애플리케이션 서버**로 요청을 전달하기 전에 특수한 *forwarded* 헤더(`X-Forwarded-*`)를 추가합니다.
이 헤더들은 그렇지 않으면 사라질 수 있는 원래 요청의 정보를 보존합니다:
* **X-Forwarded-For**: 원래 클라이언트의 IP 주소
* **X-Forwarded-Proto**: 원래 프로토콜(`https`)
* **X-Forwarded-Host**: 원래 호스트(`mysuperapp.com`)
**FastAPI CLI**를 `--forwarded-allow-ips`로 설정하면, 이 헤더를 신뢰하고 사용합니다. 예를 들어 리디렉션에서 올바른 URL을 생성하는 데 사용됩니다.
## 제거된 경로 접두사를 가진 프록시 { #proxy-with-a-stripped-path-prefix }
애플리케이션에 경로 접두사(prefix)를 추가하는 프록시를 둘 수도 있습니다.
이런 경우 `root_path`를 사용해 애플리케이션을 구성할 수 있습니다.
`root_path`는 (FastAPI가 Starlette를 통해 기반으로 하는) ASGI 사양에서 제공하는 메커니즘입니다.
`root_path`는 이러한 특정 사례를 처리하는 데 사용됩니다.
또한 서브 애플리케이션을 마운트할 때 내부적으로도 사용됩니다.
경로 접두사가 제거(stripped)되는 프록시가 있다는 것은, 코드에서는 `/app`에 경로를 선언하지만, 위에 한 겹(프록시)을 추가해 **FastAPI** 애플리케이션을 `/api/v1` 같은 경로 아래에 두는 것을 의미합니다.
이 경우 원래 경로 `/app`은 실제로 `/api/v1/app`에서 서비스됩니다.
코드는 모두 `/app`만 있다고 가정하고 작성되어 있는데도 말입니다.
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *}
그리고 프록시는 요청을 앱 서버(아마 FastAPI CLI를 통해 실행되는 Uvicorn)로 전달하기 전에, 동적으로 **경로 접두사**를 **"제거"**합니다. 그래서 애플리케이션은 여전히 `/app`에서 서비스된다고 믿게 되고, 코드 전체를 `/api/v1` 접두사를 포함하도록 수정할 필요가 없어집니다.
여기까지는 보통 정상적으로 동작합니다.
하지만 통합 문서 UI(프론트엔드)를 열면, OpenAPI 스키마를 `/api/v1/openapi.json`이 아니라 `/openapi.json`에서 가져오려고 합니다.
그래서 브라우저에서 실행되는 프론트엔드는 `/openapi.json`에 접근하려고 시도하지만 OpenAPI 스키마를 얻지 못합니다.
앱에 대해 `/api/v1` 경로 접두사를 가진 프록시가 있으므로, 프론트엔드는 `/api/v1/openapi.json`에서 OpenAPI 스키마를 가져와야 합니다.
```mermaid
graph LR
browser("Browser")
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"]
server["Server on http://127.0.0.1:8000/app"]
browser --> proxy
proxy --> server
```
/// tip | 팁
IP `0.0.0.0`은 보통 해당 머신/서버에서 사용 가능한 모든 IP에서 프로그램이 리슨한다는 의미로 사용됩니다.
///
문서 UI는 또한 OpenAPI 스키마에서 이 API `server``/api/v1`(프록시 뒤) 위치에 있다고 선언해야 합니다. 예:
```JSON hl_lines="4-8"
{
"openapi": "3.1.0",
// More stuff here
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
// More stuff here
}
}
```
이 예시에서 "Proxy"는 **Traefik** 같은 것이고, 서버는 **Uvicorn**으로 실행되는 FastAPI CLI처럼, FastAPI 애플리케이션을 실행하는 구성일 수 있습니다.
### `root_path` 제공하기 { #providing-the-root-path }
이를 달성하려면 다음처럼 커맨드 라인 옵션 `--root-path`를 사용할 수 있습니다:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Hypercorn을 사용한다면, Hypercorn에도 `--root-path` 옵션이 있습니다.
/// note | 기술 세부사항
ASGI 사양은 이 사용 사례를 위해 `root_path`를 정의합니다.
그리고 커맨드 라인 옵션 `--root-path`가 그 `root_path`를 제공합니다.
///
### 현재 `root_path` 확인하기 { #checking-the-current-root-path }
요청마다 애플리케이션에서 사용 중인 현재 `root_path`를 얻을 수 있는데, 이는 `scope` 딕셔너리(ASGI 사양의 일부)에 포함되어 있습니다.
여기서는 데모 목적을 위해 메시지에 포함하고 있습니다.
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *}
그 다음 Uvicorn을 다음과 같이 시작하면:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
응답은 다음과 비슷할 것입니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
### FastAPI 앱에서 `root_path` 설정하기 { #setting-the-root-path-in-the-fastapi-app }
또는 `--root-path` 같은 커맨드 라인 옵션(또는 동등한 방법)을 제공할 수 없는 경우, FastAPI 앱을 생성할 때 `root_path` 파라미터를 설정할 수 있습니다:
{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *}
`FastAPI`에 `root_path`를 전달하는 것은 Uvicorn이나 Hypercorn에 커맨드 라인 옵션 `--root-path`를 전달하는 것과 동일합니다.
### `root_path`에 대하여 { #about-root-path }
서버(Uvicorn)는 그 `root_path`를 앱에 전달하는 것 외에는 다른 용도로 사용하지 않는다는 점을 기억하세요.
하지만 브라우저로 <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>에 접속하면 정상 응답을 볼 수 있습니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
따라서 `http://127.0.0.1:8000/api/v1/app`로 접근될 것이라고 기대하지는 않습니다.
Uvicorn은 프록시가 `http://127.0.0.1:8000/app`에서 Uvicorn에 접근할 것을 기대하고, 그 위에 `/api/v1` 접두사를 추가하는 것은 프록시의 책임입니다.
## 제거된 경로 접두사를 가진 프록시에 대하여 { #about-proxies-with-a-stripped-path-prefix }
경로 접두사가 제거되는 프록시는 구성 방법 중 하나일 뿐이라는 점을 기억하세요.
많은 경우 기본값은 프록시가 경로 접두사를 제거하지 않는 방식일 것입니다.
그런 경우(경로 접두사를 제거하지 않는 경우) 프록시는 `https://myawesomeapp.com` 같은 곳에서 리슨하고, 브라우저가 `https://myawesomeapp.com/api/v1/app`로 접근하면, 서버(예: Uvicorn)가 `http://127.0.0.1:8000`에서 리슨하고 있을 때 프록시(경로 접두사를 제거하지 않는)는 동일한 경로로 Uvicorn에 접근합니다: `http://127.0.0.1:8000/api/v1/app`.
## Traefik으로 로컬 테스트하기 { #testing-locally-with-traefik }
<a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>을 사용하면, 경로 접두사가 제거되는 구성을 로컬에서 쉽게 실험할 수 있습니다.
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Traefik 다운로드</a>는 단일 바이너리이며, 압축 파일을 풀고 터미널에서 바로 실행할 수 있습니다.
그 다음 다음 내용을 가진 `traefik.toml` 파일을 생성하세요:
```TOML hl_lines="3"
[entryPoints]
[entryPoints.http]
address = ":9999"
[providers]
[providers.file]
filename = "routes.toml"
```
이는 Traefik이 9999 포트에서 리슨하고, 다른 파일 `routes.toml`을 사용하도록 지시합니다.
/// tip | 팁
표준 HTTP 포트 80 대신 9999 포트를 사용해서, 관리자(`sudo`) 권한으로 실행하지 않아도 되게 했습니다.
///
이제 다른 파일 `routes.toml`을 생성하세요:
```TOML hl_lines="5 12 20"
[http]
[http.middlewares]
[http.middlewares.api-stripprefix.stripPrefix]
prefixes = ["/api/v1"]
[http.routers]
[http.routers.app-http]
entryPoints = ["http"]
service = "app"
rule = "PathPrefix(`/api/v1`)"
middlewares = ["api-stripprefix"]
[http.services]
[http.services.app]
[http.services.app.loadBalancer]
[[http.services.app.loadBalancer.servers]]
url = "http://127.0.0.1:8000"
```
이 파일은 Traefik이 경로 접두사 `/api/v1`을 사용하도록 설정합니다.
그리고 Traefik은 요청을 `http://127.0.0.1:8000`에서 실행 중인 Uvicorn으로 전달합니다.
이제 Traefik을 시작하세요:
<div class="termy">
```console
$ ./traefik --configFile=traefik.toml
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
```
</div>
그리고 `--root-path` 옵션을 사용해 앱을 시작하세요:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### 응답 확인하기 { #check-the-responses }
이제 Uvicorn의 포트로 된 URL인 <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>로 접속하면 정상 응답을 볼 수 있습니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
/// tip | 팁
`http://127.0.0.1:8000/app`로 접근했는데도 `/api/v1`의 `root_path`가 표시되는 것에 주의하세요. 이는 옵션 `--root-path`에서 가져온 값입니다.
///
이제 Traefik의 포트가 포함되고 경로 접두사가 포함된 URL <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>을 여세요.
동일한 응답을 얻습니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
하지만 이번에는 프록시가 제공한 접두사 경로 `/api/v1`이 포함된 URL에서의 응답입니다.
물론 여기서의 아이디어는 모두가 프록시를 통해 앱에 접근한다는 것이므로, `/api/v1` 경로 접두사가 있는 버전이 "올바른" 접근입니다.
그리고 경로 접두사가 없는 버전(`http://127.0.0.1:8000/app`)은 Uvicorn이 직접 제공하는 것이며, 오직 _프록시_(Traefik)가 접근하기 위한 용도입니다.
이는 프록시(Traefik)가 경로 접두사를 어떻게 사용하는지, 그리고 서버(Uvicorn)가 옵션 `--root-path`로부터의 `root_path`를 어떻게 사용하는지를 보여줍니다.
### 문서 UI 확인하기 { #check-the-docs-ui }
하지만 재미있는 부분은 여기입니다. ✨
앱에 접근하는 "공식" 방법은 우리가 정의한 경로 접두사를 가진 프록시를 통해서입니다. 따라서 기대하는 대로, URL에 경로 접두사가 없는 상태에서 Uvicorn이 직접 제공하는 docs UI를 시도하면, 프록시를 통해 접근된다고 가정하고 있기 때문에 동작하지 않습니다.
<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>에서 확인할 수 있습니다:
<img src="/img/tutorial/behind-a-proxy/image01.png">
하지만 프록시(포트 `9999`)를 사용해 "공식" URL인 `/api/v1/docs`에서 docs UI에 접근하면, 올바르게 동작합니다! 🎉
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>에서 확인할 수 있습니다:
<img src="/img/tutorial/behind-a-proxy/image02.png">
원하던 그대로입니다. ✔️
이는 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에 주목하세요.
///
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>의 docs UI에서는 다음처럼 보입니다:
<img src="/img/tutorial/behind-a-proxy/image03.png">
/// 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`를 똑똑하게 사용하므로, 그냥 동작합니다. ✨

View File

@@ -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 }
예를 들어, 성능을 극대화하려는 경우, <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">orjson</a>을 설치하여 사용하고 응답을 `ORJSONResponse`로 설정할 수 있습니다.
예를 들어, 성능을 극대화하려는 경우, <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>을 설치하여 사용하고 응답을 `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을 문서화 하는 방법을 알 수 있습니다.
<img src="/img/tutorial/custom-response/image01.png">
## 사용 가능한 응답들
## 사용 가능한 응답들 { #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 }
<a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>을 사용하여 빠른 JSON 응답을 제공하는 대안입니다. 위에서 설명한 내용과 같습니다.
@@ -164,13 +164,13 @@ FastAPI (실제로는 Starlette)가 자동으로 `Content-Length` 헤더를 포
///
### `UJSONResponse`
### `UJSONResponse` { #ujsonresponse }
<a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>을 사용한 또 다른 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)하는 제너레이터 함수를 만들 수 있습니다.
<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> 객체(예: `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` 클래스에서 사용되지 않는 설정으로 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">orjson</a>을 사용하고 싶다고 가정해봅시다.
예를 들어, 포함된 `ORJSONResponse` 클래스에서 사용되지 않는 설정으로 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>을 사용하고 싶다고 가정해봅시다.
만약 들여쓰기 및 포맷된 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}.

View File

@@ -0,0 +1,95 @@
# Dataclasses 사용하기 { #using-dataclasses }
FastAPI는 **Pydantic** 위에 구축되어 있으며, 지금까지는 Pydantic 모델을 사용해 요청과 응답을 선언하는 방법을 보여드렸습니다.
하지만 FastAPI는 <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>도 같은 방식으로 사용하는 것을 지원합니다:
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
이는 **Pydantic** 덕분에 여전히 지원되는데, Pydantic이 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">`dataclasses`에 대한 내부 지원</a>을 제공하기 때문입니다.
따라서 위 코드처럼 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 사용자 인터페이스에 표시됩니다:
<img src="/img/tutorial/dataclasses/image01.png">
## 중첩 데이터 구조에서 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으로 <abbr title="converting the data to a format that can be transmitted - 데이터를 전송 가능한 형식으로 변환하는 것">serializing</abbr>할 수 있습니다.
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 모델과 조합하거나, 이를 상속하거나, 여러분의 모델에 포함하는 등의 작업도 할 수 있습니다.
자세한 내용은 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">dataclasses에 관한 Pydantic 문서</a>를 참고하세요.
## 버전 { #version }
이 기능은 FastAPI `0.67.0` 버전부터 사용할 수 있습니다. 🔖

View File

@@ -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 기술 사양에 따르면, 이는 <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>의 일부이며, `startup``shutdown`이라는 이벤트를 정의합니다.
내부적으로 ASGI 기술 사양에서는 이것이 <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>의 일부이며, `startup``shutdown`이라는 이벤트를 정의합니다.
/// info | 정보
Starlette `lifespan` 핸들러에 대해 더 읽고 싶다면 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette의 Lifespan 문서</a>에서 확인할 수 있습니다.
Starlette `lifespan` 핸들러에 대해서는 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette의 Lifespan 문서</a>에서 더 읽어볼 수 있습니다.
이 문서에는 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다.
또한 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다.
///
## 서브 애플리케이션
## 서브 애플리케이션 { #sub-applications }
🚨 이 lifespan 이벤트(`startup``shutdown`)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요.
🚨 이 lifespan 이벤트(startupshutdown)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요.

View File

@@ -0,0 +1,208 @@
# SDK 생성하기 { #generating-sdks }
**FastAPI**는 **OpenAPI** 사양을 기반으로 하므로, FastAPI의 API는 많은 도구가 이해할 수 있는 표준 형식으로 설명할 수 있습니다.
덕분에 여러 언어용 클라이언트 라이브러리(<abbr title="Software Development Kits - 소프트웨어 개발 키트">**SDKs**</abbr>), 최신 **문서**, 그리고 코드와 동기화된 **테스트** 또는 **자동화 워크플로**를 쉽게 생성할 수 있습니다.
이 가이드에서는 FastAPI 백엔드용 **TypeScript SDK**를 생성하는 방법을 배웁니다.
## 오픈 소스 SDK 생성기 { #open-source-sdk-generators }
다양하게 활용할 수 있는 옵션으로 <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>가 있으며, **다양한 프로그래밍 언어**를 지원하고 OpenAPI 사양으로부터 SDK를 생성할 수 있습니다.
**TypeScript 클라이언트**의 경우 <a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a>는 TypeScript 생태계에 최적화된 경험을 제공하는 목적에 맞게 설계된 솔루션입니다.
더 많은 SDK 생성기는 <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a>에서 확인할 수 있습니다.
/// 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를 지원하는 데에도 관심이 있음을 나타냅니다. 🙇
예를 들어 다음을 사용해 볼 수 있습니다:
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
이 중 일부는 오픈 소스이거나 무료 티어를 제공하므로, 비용 부담 없이 사용해 볼 수 있습니다. 다른 상용 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)**가 있는 것을 볼 수 있습니다:
<img src="/img/tutorial/generate-clients/image01.png">
이 스키마는 앱에서 모델로 선언되었기 때문에 볼 수 있습니다.
그 정보는 앱의 **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를 생성합니다.
<a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">`@hey-api/openapi-ts` 설치 방법</a>과 <a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">생성된 결과물</a>은 해당 웹사이트에서 확인할 수 있습니다.
### SDK 사용하기 { #using-the-sdk }
이제 클라이언트 코드를 import해서 사용할 수 있습니다. 아래처럼 사용할 수 있으며, 메서드에 대한 자동 완성이 제공되는 것을 확인할 수 있습니다:
<img src="/img/tutorial/generate-clients/image02.png">
보낼 페이로드에 대해서도 자동 완성이 제공됩니다:
<img src="/img/tutorial/generate-clients/image03.png">
/// tip | 팁
`name``price`에 대한 자동 완성은 FastAPI 애플리케이션에서 `Item` 모델에 정의된 내용입니다.
///
전송하는 데이터에 대해 인라인 오류도 표시됩니다:
<img src="/img/tutorial/generate-clients/image04.png">
응답 객체도 자동 완성을 제공합니다:
<img src="/img/tutorial/generate-clients/image05.png">
## 태그가 있는 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 앱에 대해 클라이언트를 생성하면, 일반적으로 생성된 클라이언트 코드도 태그를 기준으로 분리됩니다.
이렇게 하면 클라이언트 코드에서 항목들이 올바르게 정렬되고 그룹화됩니다:
<img src="/img/tutorial/generate-clients/image06.png">
이 경우 다음이 있습니다:
* `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 }
이제 클라이언트를 다시 생성하면, 개선된 메서드 이름을 확인할 수 있습니다:
<img src="/img/tutorial/generate-clients/image07.png">
보시다시피, 이제 메서드 이름은 태그 다음에 함수 이름이 오며, 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
```
새 클라이언트를 생성한 후에는 **깔끔한 메서드 이름**을 가지면서도, **자동 완성**, **인라인 오류** 등은 그대로 제공됩니다:
<img src="/img/tutorial/generate-clients/image08.png">
## 장점 { #benefits }
자동으로 생성된 클라이언트를 사용하면 다음에 대해 **자동 완성**을 받을 수 있습니다:
* 메서드
* 본문(body)의 요청 페이로드, 쿼리 파라미터 등
* 응답 페이로드
또한 모든 것에 대해 **인라인 오류**도 확인할 수 있습니다.
그리고 백엔드 코드를 업데이트한 뒤 프론트엔드를 **재생성(regenerate)**하면, 새 *path operations*가 메서드로 추가되고 기존 것은 제거되며, 그 밖의 변경 사항도 생성된 코드에 반영됩니다. 🤓
이는 무언가 변경되면 그 변경이 클라이언트 코드에도 자동으로 **반영**된다는 뜻입니다. 또한 클라이언트를 **빌드(build)**하면 사용된 데이터가 **불일치(mismatch)**할 경우 오류가 발생합니다.
따라서 운영 환경에서 최종 사용자에게 오류가 노출된 뒤 문제를 추적하는 대신, 개발 사이클 초기에 **많은 오류를 매우 빨리 감지**할 수 있습니다. ✨

View File

@@ -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**의 <a href="https://testdriven.io/courses/tdd-fastapi/" class="external-link" target="_blank">FastAPI와 Docker를 사용한 테스트 주도 개발</a>.
그들은 현재 전체 수익의 10퍼센트를 **FastAPI** 개발에 기부하고 있습니다. 🎉 😄

View File

@@ -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를 기반으로 하고 <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr> 사양을 구현하므로, 어떤 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도 많이 있습니다.
예를 들어:
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn의 `ProxyHeadersMiddleware`</a>
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
사용 가능한 다른 middleware를 보려면 <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette의 Middleware 문서</a>와 <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a>를 확인하세요.

View File

@@ -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의 <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> 타입을 사용합니다.
///
유일하게 새로운 것은 *경로 처리 데코레이터*의 인자로 `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 요청입니다.
콜백을 직접 구현할 때는 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>나 <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a> 같은 것을 사용할 수 있습니다.
///
## 콜백 문서화 코드 작성하기 { #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*에는 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a>(자세한 내용은 아래 참고)이 포함될 수 있으며, 이를 통해 *당신의 API*로 보내진 원래 요청의 파라미터와 일부 값을 변수로 사용할 수 있습니다.
### 콜백 경로 표현식 { #the-callback-path-expression }
콜백 *path*는 *당신의 API*로 보내진 원래 요청의 일부를 포함할 수 있는 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a>을 가질 수 있습니다.
이 경우, 다음 `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 }
이제 앱을 실행하고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>로 이동하세요.
*경로 처리*에 대해 "Callbacks" 섹션을 포함한 문서가 표시되며, *external API*가 어떤 형태여야 하는지 확인할 수 있습니다:
<img src="/img/tutorial/openapi-callbacks/image01.png">

View File

@@ -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 }
이제 앱을 실행하고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>로 이동하세요.
문서에 일반적인 *경로 처리*가 보이고, 이제는 일부 **webhooks**도 함께 보일 것입니다:
<img src="/img/tutorial/openapi-webhooks/image01.png">

View File

@@ -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 명세에서는 이를 <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>라고 부릅니다.
///
여기에는 *경로 처리*에 대한 모든 정보가 있으며, 자동 문서를 생성하는 데 사용됩니다.
`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 문서를 열면, 해당 특정 *경로 처리*의 하단에 확장이 표시됩니다.
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
또한 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으로 <abbr title="converted from some plain format, like bytes, into Python objects - bytes 같은 일반 형식에서 Python 객체로 변환">parsed</abbr>되지도 않고, `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 모델을 재사용합니다.
하지만 마찬가지로, 다른 방식으로 검증할 수도 있습니다.
///

View File

@@ -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` 파라미터를 선언하고 그 안에서 상태 코드를 설정할 수 있습니다. 단, 마지막으로 설정된 상태 코드가 우선 적용된다는 점을 유의하세요.

View File

@@ -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`로도 제공합니다.
///
사용 가능한 모든 매개변수와 옵션은 <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette 문서</a>에서 확인할 수 있습니다.
사용 가능한 모든 매개변수와 옵션은 <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette 문서</a>에서 확인할 수 있습니다.

View File

@@ -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`로 변환하는 등의 작업을 모두 수행해 주었을 것이기 때문입니다.
예를 들어 <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> 응답을 반환하고 싶다고 가정해보겠습니다.
이제, 이를 사용해 사용자 정의 응답을 반환하는 방법을 알아보겠습니다.
예를 들어 <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> 응답을 반환하고 싶다고 가정해 보겠습니다.
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`사용하는/선언하는 방법을 확인할 수 있습니다.

View File

@@ -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 }
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">X- 접두어를 사용하여</a> 커스텀 사설 헤더를 추가할 수 있습니다.
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">`X-` 접두어를 사용하여</a> 커스텀 사설 헤더를 추가할 수 있다는 점을 기억하세요.
하지만, 여러분이 브라우저에서 클라이언트가 볼 수 있기를 원하는 커스텀 헤더가 있는 경우, CORS 설정에 이를 추가해야 합니다([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}에서 자세히 알아보세요). `expose_headers` 매개변수를 사용하여 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette의 CORS 설명서</a>에 문서화된 대로 설정할 수 있습니다.
하지만, 여러분이 브라우저에서 클라이언트가 볼 수 있기를 원하는 커스텀 헤더가 있는 경우, CORS 설정에 이를 추가해야 합니다([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}에서 자세히 알아보세요). <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette의 CORS 서</a>에 문서화된 `expose_headers` 매개변수를 사용하세요.

View File

@@ -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" 버튼을 클릭하면) 브라우저가 사용자명과 비밀번호를 물어봅니다:
<img src="/img/tutorial/security/image12.png">
## 사용자명 확인하기 { #check-the-username }
더 완전한 예시입니다.
dependency를 사용해 사용자명과 비밀번호가 올바른지 확인하세요.
이를 위해 Python 표준 모듈 <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a>를 사용해 사용자명과 비밀번호를 확인합니다.
`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] *}

View File

@@ -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}을 이미 읽었다고 가정합니다.
모두 동일한 개념을 기반으로 하지만, 몇 가지 추가 기능을 사용할 수 있게 해줍니다.

View File

@@ -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 등으로 로그인하면서 권한을 부여할 때 사용되는 것과 동일한 메커니즘입니다:
<img src="/img/tutorial/security/image11.png">
## 스코프를 포함한 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 문서를 열면, 인증하고 인가할 스코프를 지정할 수 있습니다.
<img src="/img/tutorial/security/image11.png">
어떤 스코프도 선택하지 않으면 “인증”은 되지만, `/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`를 사용할 수도 있습니다.

View File

@@ -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은 <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>를 통해 환경 변수에서 오는 이러한 설정을 처리할 수 있는 훌륭한 유틸리티를 제공합니다.
### `pydantic-settings` 설치하기 { #install-pydantic-settings }
먼저 [가상 환경](../virtual-environments.md){.internal-link target=_blank}을 만들고 활성화한 다음, `pydantic-settings` 패키지를 설치하세요:
<div class="termy">
```console
$ pip install pydantic-settings
---> 100%
```
</div>
또는 다음처럼 `all` extras를 설치하면 함께 포함됩니다:
<div class="termy">
```console
$ pip install "fastapi[all]"
---> 100%
```
</div>
### `Settings` 객체 만들기 { #create-the-settings-object }
Pydantic에서 `BaseSettings`를 import하고, Pydantic 모델과 매우 비슷하게 서브클래스를 만드세요.
Pydantic 모델과 같은 방식으로, 타입 어노테이션(그리고 필요하다면 기본값)과 함께 클래스 속성을 선언합니다.
다양한 데이터 타입, `Field()`로 추가 검증 등 Pydantic 모델에서 사용하는 동일한 검증 기능과 도구를 모두 사용할 수 있습니다.
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
/// tip | 팁
빠르게 복사/붙여넣기할 예시가 필요하다면, 이 예시는 사용하지 말고 아래의 마지막 예시를 사용하세요.
///
그 다음, 해당 `Settings` 클래스의 인스턴스(여기서는 `settings` 객체)를 생성하면 Pydantic이 대소문자를 구분하지 않고 환경 변수를 읽습니다. 따라서 대문자 변수 `APP_NAME``app_name` 속성에 대해 읽힙니다.
이후 데이터를 변환하고 검증합니다. 그래서 그 `settings` 객체를 사용할 때는 선언한 타입의 데이터를 갖게 됩니다(예: `items_per_user``int`가 됩니다).
### `settings` 사용하기 { #use-the-settings }
이제 애플리케이션에서 새 `settings` 객체를 사용할 수 있습니다:
{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *}
### 서버 실행하기 { #run-the-server }
다음으로 환경 변수를 통해 구성을 전달하면서 서버를 실행합니다. 예를 들어 다음처럼 `ADMIN_EMAIL``APP_NAME`을 설정할 수 있습니다:
<div class="termy">
```console
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
/// tip | 팁
하나의 명령에 여러 env var를 설정하려면 공백으로 구분하고, 모두 명령 앞에 두세요.
///
그러면 `admin_email` 설정은 `"deadpool@example.com"`으로 설정됩니다.
`app_name``"ChimichangApp"`이 됩니다.
그리고 `items_per_user`는 기본값 `50`을 유지합니다.
## 다른 모듈의 설정 { #settings-in-another-module }
[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}에서 본 것처럼, 설정을 다른 모듈 파일에 넣을 수도 있습니다.
예를 들어 `config.py` 파일을 다음처럼 만들 수 있습니다:
{* ../../docs_src/settings/app01_py39/config.py *}
그리고 `main.py` 파일에서 이를 사용합니다:
{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *}
/// tip | 팁
[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}에서 본 것처럼 `__init__.py` 파일도 필요합니다.
///
## 의존성에서 설정 사용하기 { #settings-in-a-dependency }
어떤 경우에는 어디서나 사용되는 전역 `settings` 객체를 두는 대신, 의존성에서 설정을 제공하는 것이 유용할 수 있습니다.
이는 특히 테스트 중에 유용할 수 있는데, 사용자 정의 설정으로 의존성을 override하기가 매우 쉽기 때문입니다.
### config 파일 { #the-config-file }
이전 예시에서 이어서, `config.py` 파일은 다음과 같을 수 있습니다:
{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *}
이제는 기본 인스턴스 `settings = Settings()`를 생성하지 않는다는 점에 유의하세요.
### 메인 앱 파일 { #the-main-app-file }
이제 새로운 `config.Settings()`를 반환하는 의존성을 생성합니다.
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// tip | 팁
`@lru_cache`는 조금 뒤에 다룹니다.
지금은 `get_settings()`가 일반 함수라고 가정해도 됩니다.
///
그 다음 *경로 처리 함수*에서 이를 의존성으로 요구하고, 필요한 어디서든 사용할 수 있습니다.
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *}
### 설정과 테스트 { #settings-and-testing }
그 다음, `get_settings`에 대한 의존성 override를 만들어 테스트 중에 다른 설정 객체를 제공하기가 매우 쉬워집니다:
{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *}
의존성 override에서는 새 `Settings` 객체를 생성할 때 `admin_email`의 새 값을 설정하고, 그 새 객체를 반환합니다.
그 다음 그것이 사용되는지 테스트할 수 있습니다.
## `.env` 파일 읽기 { #reading-a-env-file }
많이 바뀔 수 있는 설정이 많고, 서로 다른 환경에서 사용한다면, 이를 파일에 넣어 환경 변수인 것처럼 읽는 것이 유용할 수 있습니다.
이 관행은 충분히 흔해서 이름도 있는데, 이러한 환경 변수들은 보통 `.env` 파일에 두며, 그 파일을 "dotenv"라고 부릅니다.
/// tip | 팁
점(`.`)으로 시작하는 파일은 Linux, macOS 같은 Unix 계열 시스템에서 숨김 파일입니다.
하지만 dotenv 파일이 꼭 그 정확한 파일명을 가져야 하는 것은 아닙니다.
///
Pydantic은 외부 라이브러리를 사용해 이런 유형의 파일에서 읽는 기능을 지원합니다. 자세한 내용은 <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>를 참고하세요.
/// tip | 팁
이를 사용하려면 `pip install python-dotenv`가 필요합니다.
///
### `.env` 파일 { #the-env-file }
다음과 같은 `.env` 파일을 둘 수 있습니다:
```bash
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
```
### `.env`에서 설정 읽기 { #read-settings-from-env }
그리고 `config.py`를 다음처럼 업데이트합니다:
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
/// tip | 팁
`model_config` 속성은 Pydantic 설정을 위한 것입니다. 자세한 내용은 <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a>을 참고하세요.
///
여기서는 Pydantic `Settings` 클래스 안에 config `env_file`을 정의하고, 사용하려는 dotenv 파일의 파일명을 값으로 설정합니다.
### `lru_cache`로 `Settings`를 한 번만 생성하기 { #creating-the-settings-only-once-with-lru-cache }
디스크에서 파일을 읽는 것은 보통 비용이 큰(느린) 작업이므로, 각 요청마다 읽기보다는 한 번만 수행하고 동일한 settings 객체를 재사용하는 것이 좋습니다.
하지만 매번 다음을 수행하면:
```Python
Settings()
```
`Settings` 객체가 생성되고, 생성 시점에 `.env` 파일을 다시 읽게 됩니다.
의존성 함수가 단순히 다음과 같다면:
```Python
def get_settings():
return Settings()
```
요청마다 객체를 생성하게 되고, 요청마다 `.env` 파일을 읽게 됩니다. ⚠️
하지만 위에 `@lru_cache` 데코레이터를 사용하고 있으므로, `Settings` 객체는 최초 호출 시 딱 한 번만 생성됩니다. ✔️
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *}
그 다음 요청들에서 의존성으로 `get_settings()`가 다시 호출될 때마다, `get_settings()`의 내부 코드를 실행해서 새 `Settings` 객체를 만드는 대신, 첫 호출에서 반환된 동일한 객체를 계속 반환합니다.
#### `lru_cache` Technical Details { #lru-cache-technical-details }
`@lru_cache`는 데코레이션한 함수가 매번 다시 계산하는 대신, 첫 번째에 반환된 동일한 값을 반환하도록 함수를 수정합니다(즉, 매번 함수 코드를 실행하지 않습니다).
따라서 아래의 함수는 인자 조합마다 한 번씩 실행됩니다. 그리고 각각의 인자 조합에 대해 반환된 값은, 함수가 정확히 같은 인자 조합으로 호출될 때마다 반복해서 사용됩니다.
예를 들어 다음 함수가 있다면:
```Python
@lru_cache
def say_hi(name: str, salutation: str = "Ms."):
return f"Hello {salutation} {name}"
```
프로그램은 다음과 같이 실행될 수 있습니다:
```mermaid
sequenceDiagram
participant code as Code
participant function as say_hi()
participant execute as Execute function
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Camila")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick", salutation="Mr.")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Rick")
function ->> code: return stored result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
```
우리의 의존성 `get_settings()`의 경우, 함수가 어떤 인자도 받지 않으므로 항상 같은 값을 반환합니다.
이렇게 하면 거의 전역 변수처럼 동작합니다. 하지만 의존성 함수를 사용하므로 테스트를 위해 쉽게 override할 수 있습니다.
`@lru_cache`는 Python 표준 라이브러리의 `functools`에 포함되어 있으며, 자세한 내용은 <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">`@lru_cache`에 대한 Python 문서</a>에서 확인할 수 있습니다.
## 정리 { #recap }
Pydantic Settings를 사용하면 Pydantic 모델의 모든 강력한 기능을 활용해 애플리케이션의 설정 또는 구성을 처리할 수 있습니다.
* 의존성을 사용하면 테스트를 단순화할 수 있습니다.
* `.env` 파일을 사용할 수 있습니다.
* `@lru_cache`를 사용하면 각 요청마다 dotenv 파일을 반복해서 읽는 것을 피하면서도, 테스트 중에는 이를 override할 수 있습니다.

View File

@@ -1,67 +1,67 @@
# 하위 응용프로그램 - 마운트
# 하위 응용프로그램 - 마운트 { #sub-applications-mounts }
만약 각각의 독립적인 OpenAPI와 문서 UI를 갖는 두 개의 독립적인 FastAPI 응용프로그램이 필요하다면, 메인 어플리케이션에 하나 (또는 그 이상의) 하위-응용프로그램(들)마운트"해서 사용할 수 있습니다.
각각의 독립적인 OpenAPI와 문서 UI를 갖는 두 개의 독립적인 FastAPI 애플리케이션이 필요하다면, 메인 앱을 두고 하나(또는 그 이상)의 하위 응용프로그램을 "마운트"할 수 있습니다.
## **FastAPI** 응용프로그램 마운트
## **FastAPI** 애플리케이션 마운트 { #mounting-a-fastapi-application }
마운트"란 완전히 독립적인" 응용프로그램을 특정 경로에 추가하여 해당 하위 응용프로그램에 선언된 *경로 동작*을 통해 해당 경로 아래에 있는 모든 작업들을 처리할 수 있도록 하는 것을 의미합니다.
"마운트"란 완전히 "독립적인" 애플리케이션을 특정 경로에 추가하고, 그 하위 응용프로그램에 선언된 _경로 처리_로 해당 경로 아래 모든 을 처리도록 하는 것을 의미합니다.
### 최상단 응용프로그램
### 최상위 애플리케이션 { #top-level-application }
먼저, 메인, 최상단의 **FastAPI** 응용프로그램과 이것의 *경로 동작*을 생성합니다:
먼저, 메인 최상 **FastAPI** 애플리케이션과 그 *경로 처리*를 생성합니다:
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *}
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *}
### 하위 응용프로그램
### 하위 응용프로그램 { #sub-application }
다음으로, 하위 응용프로그램과 이것의 *경로 동작*을 생성합니다:
다음, 하위 응용프로그램과 *경로 처리*를 생성합니다.
이 하위 응용프로그램은 또 다른 표준 FastAPI 응용프로그램입니다. 다만 이것은 “마운트입니다:
이 하위 응용프로그램은 또 다른 표준 FastAPI 애플리케이션이지만, "마운트"애플리케이션입니다:
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *}
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *}
### 하위 응용프로그램 마운트
### 하위 응용프로그램 마운트 { #mount-the-sub-application }
최상단 응용프로그램, `app`에 하위 응용프로그램, `subapi`를 마운트합니다.
최상위 애플리케이션 `app` 하위 응용프로그램 `subapi`를 마운트합니다.
예시에서, 하위 응용프로그램션은 `/subapi` 경로에 마운트 될 것입니다:
경우 `/subapi` 경로에 마운트니다:
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *}
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *}
### 자동으로 생성된 API 문서 확인
### 자동 API 문서 확인 { #check-the-automatic-api-docs }
이제, `uvicorn`으로 메인 응용프로그램을 실행하십시오. 당신의 파일이 `main.py`라면, 이렇게 실행합니다:
이제 파일과 함께 `fastapi` 명령을 실행하세요:
<div class="termy">
```console
$ uvicorn main:app --reload
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
그리고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>에서 문서를 여십시오.
그리고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>에서 문서를 여세요.
메인 응용프로그램의 *경로 동작*만을 포함하는, 메인 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다:
메인 앱의 자동 API 문서를 보게 될 것이며, 메인 앱 자체의 _경로 처리_만 포함됩니다:
<img src="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image01.png">
<img src="/img/tutorial/sub-applications/image01.png">
다음으로, <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>에서 하위 응용프로그램의 문서를 여십시오.
다음, <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>에서 하위 응용프로그램의 문서를 여세요.
하위 경로 접두사 `/subapi` 아래에 선언된 *경로 동작* 을 포함하는, 하위 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다:
하위 응용프로그램의 자동 API 문서를 보게 될 것이며, 하위 경로 접두사 `/subapi` 아래에 올바르게 포함된 하위 응용프로그램 자체의 _경로 처리_만 포함됩니다:
<img src="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image02.png">
<img src="/img/tutorial/sub-applications/image02.png">
두 사용자 인터페이스 중 어느 하나를 사용해야하는 경우, 브라우저 특정 응용프로그램 또는 하위 응용프로그램과 각각 통신할 수 있기 때문에 올바르게 동작할 것입니다.
두 사용자 인터페이스 중 어느 것과 상호작용을 시도하더라도 올바르게 동작할 것입니다. 브라우저가 각 특정 또는 하위 앱과 통신할 수 있기 때문입니다.
### 기술적 세부사항: `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} 섹션에서 더 알아볼 수 있습니다.

View File

@@ -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`를 설치해야 합니다:
<div class="termy">
@@ -21,39 +20,38 @@ $ pip install jinja2
</div>
## 사용하기 `Jinja2Templates`
## `Jinja2Templates` 사용하기 { #using-jinja2templates }
* `Jinja2Templates`를 가져옵니다.
* 나중에 재사용할 수 있는 `templates` 객체를 생성합니다.
* 템플릿을 반환할 경로 작업`Request` 매개변수를 선언합니다.
* 템플릿을 반환할 *경로 처리*`Request` 매개변수를 선언합니다.
* 생성한 `templates`를 사용하여 `TemplateResponse`를 렌더링하고 반환합니다. 템플릿의 이름, 요청 객체 및 Jinja2 템플릿 내에서 사용될 키-값 쌍이 포함된 "컨텍스트" 딕셔너리도 전달합니다.
```Python hl_lines="4 11 15-18"
{!../../docs_src/templates/tutorial001.py!}
```
{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *}
/// note | 참고
FastAPI 0.108.0 이전 Starlette 0.29.0에서는 `name`이 첫 번째 매개변수였습니다.
FastAPI 0.108.0 이전, Starlette 0.29.0에서는 `name`이 첫 번째 매개변수였습니다.
또한 이전 버전에서는 `request` 객체가 Jinja2의 컨텍스트에서 키-값 쌍의 일부로 전달되었습니다.
또한 이전 버전에서는 `request` 객체가 Jinja2의 컨텍스트에서 키-값 쌍의 일부로 전달되었습니다.
///
/// tip | 팁
`response_class=HTMLResponse`를 선언하면 문서 UI 응답이 HTML임을 알 수 있습니다.
`response_class=HTMLResponse`를 선언하면 문서 UI 응답이 HTML임을 알 수 있습니다.
///
/// note | 기술 세부 사항
/// note | 기술 세부사항
`from starlette.templating import Jinja2Templates`를 사용할 수도 있습니다.
**FastAPI**는 개발자를 위한 편리함으로 `fastapi.templating` 대신 `starlette.templating`을 제공합니다. 하지만 대부분의 사용 가능한 응답은 Starlette에서 직접 옵니다. `Request` 및 `StaticFiles`도 마찬가지입니다.
**FastAPI**는 개발자를 위한 편리함으로 `fastapi.templating`과 동일하게 `starlette.templating`을 제공합니다. 하지만 대부분의 사용 가능한 응답은 Starlette에서 직접 옵니다. `Request``StaticFiles`도 마찬가지입니다.
///
## 템플릿 작성하기
## 템플릿 작성하기 { #writing-templates }
그런 다음 `templates/item.html`에 템플릿을 작성할 수 있습니다. 예를 들면:
@@ -61,7 +59,7 @@ FastAPI 0.108.0 이전과 Starlette 0.29.0에서는 `name`이 첫 번째 매개
{!../../docs_src/templates/templates/item.html!}
```
### 템플릿 컨텍스트 값
### 템플릿 컨텍스트 값 { #template-context-values }
다음과 같은 HTML에서:
@@ -85,9 +83,9 @@ Item ID: {{ id }}
Item ID: 42
```
### 템플릿 `url_for` 인수
### 템플릿 `url_for` 인수 { #template-url-for-arguments }
템플릿 내에서 `url_for()`를 사용할 수도 있으며, 이는 *경로 작업 함수*에서 사용될 인수와 동일한 인수를 받습니다.
템플릿 내에서 `url_for()`를 사용할 수도 있으며, 이는 *경로 처리 함수*에서 사용될 인수와 동일한 인수를 받습니다.
따라서 다음과 같은 부분에서:
@@ -99,14 +97,15 @@ Item ID: 42
{% endraw %}
...이는 *경로 작업 함수* `read_item(id=id)`가 처리할 동일한 URL로 링크를 생성합니다.
...이는 *경로 처리 함수* `read_item(id=id)`가 처리할 동일한 URL로 링크를 생성합니다.
예를 들어, ID가 `42`일 경우, 이는 다음과 같이 렌더링됩니다:
```html
<a href="/items/42">
```
## 템플릿과 정적 파일
## 템플릿과 정적 파일 { #templates-and-static-files }
템플릿 내에서 `url_for()`를 사용할 수 있으며, 예를 들어 `name="static"`으로 마운트한 `StaticFiles`와 함께 사용할 수 있습니다.
@@ -114,7 +113,7 @@ Item ID: 42
{!../../docs_src/templates/templates/item.html!}
```
이 예제에서는 `static/styles.css`에 있는 CSS 파일에 연결될 것입니다:
이 예제에서는 다음을 통해 `static/styles.css`에 있는 CSS 파일에 링크합니다:
```CSS hl_lines="4"
{!../../docs_src/templates/static/styles.css!}
@@ -122,6 +121,6 @@ Item ID: 42
그리고 `StaticFiles`를 사용하고 있으므로, 해당 CSS 파일은 **FastAPI** 애플리케이션에서 `/static/styles.css` URL로 자동 제공됩니다.
## 더 많은 세부 사항
## 더 많은 세부 사항 { #more-details }
템플릿 테스트를 포함한 더 많은 세부 사항은 <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">Starlette의 템플릿 문서</a>를 확인하세요.

View File

@@ -1,14 +1,14 @@
# 테스트 의존성 오버라이드
# 오버라이드로 의존성 테스트하기 { #testing-dependencies-with-overrides }
## 테스트 중 의존성 오버라이드하기
## 테스트 중 의존성 오버라이드하기 { #overriding-dependencies-during-testing }
테스트를 진행하다 보면 의존성을 오버라이드해야 하는 경우가 있습니다.
테스트를 진행하다 보면 테스트 중에 의존성을 오버라이드해야 하는 경우가 있습니다.
원래 의존성을 실행하고 싶지 않을 수도 있습니다(또는 그 의존성이 가지고 있는 하위 의존성까지도 실행되지 않길 원할 수 있습니다).
대신, 테스트 동안(특정 테스트에서만) 사용될 다른 의존성을 제공하고, 원래 의존성이 사용되던 곳에서 사용할 수 있는 값을 제공하기를 원할 수 있습니다.
### 사용 사례: 외부 서비스
### 사용 사례: 외부 서비스 { #use-cases-external-service }
예를 들어, 외부 인증 제공자를 호출해야 하는 경우를 생각해봅시다.
@@ -18,11 +18,11 @@
외부 제공자를 한 번만 테스트하고 싶을 수도 있지만 테스트를 실행할 때마다 반드시 호출할 필요는 없습니다.
이 경우 해당 공급자를 호출하는 종속성을 오버라이드하고 테스트에 대해서만 모의 사용자를 반환하는 사용자 지정 종속성을 사용할 수 있습니다.
이 경우 해당 공급자를 호출하는 의존성을 오버라이드하고 테스트에 대해서만 모의 사용자를 반환하는 사용자 지정 의존성을 사용할 수 있습니다.
### `app.dependency_overrides` 속성 사용하기
### `app.dependency_overrides` 속성 사용하기 { #use-the-app-dependency-overrides-attribute }
이런 경우를 위해 **FastAPI** 응용 프로그램에는 `app.dependency_overrides`라는 속성이 있습니다. 이는 간단한 `dict`입니다.
이런 경우를 위해 **FastAPI** 애플리케이션에는 `app.dependency_overrides`라는 속성이 있습니다. 이는 간단한 `dict`입니다.
테스트를 위해 의존성을 오버라이드하려면, 원래 의존성(함수)을 키로 설정하고 오버라이드할 의존성(다른 함수)을 값으로 설정합니다.
@@ -34,7 +34,7 @@
**FastAPI** 애플리케이션 어디에서든 사용된 의존성에 대해 오버라이드를 설정할 수 있습니다.
원래 의존성은 *경로 동작 함수*, *경로 동작 데코레이터*(반환값을 사용하지 않는 경우), `.include_router()` 호출 등에서 사용될 수 있습니다.
원래 의존성은 *경로 처리 함수*, *경로 처리 데코레이터*(반환값을 사용하지 않는 경우), `.include_router()` 호출 등에서 사용될 수 있습니다.
FastAPI는 여전히 이를 오버라이드할 수 있습니다.
@@ -42,7 +42,7 @@ FastAPI는 여전히 이를 오버라이드할 수 있습니다.
그런 다음, `app.dependency_overrides`를 빈 `dict`로 설정하여 오버라이드를 재설정(제거)할 수 있습니다:
```python
```Python
app.dependency_overrides = {}
```

View File

@@ -1,5 +1,12 @@
# 이벤트 테스트: 시작 - 종료
# 이벤트 테스트: 라이프스팬 및 시작 - 종료 { #testing-events-lifespan-and-startup-shutdown }
테스트에서 이벤트 핸들러(`startup``shutdown`)를 실행해야 하는 경우, `with` 문과 함께 `TestClient`를 사용할 수 있습니다.
테스트에서 `lifespan` 실행해야 하는 경우, `with` 문과 함께 `TestClient`를 사용할 수 있습니다:
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *}
{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *}
["공식 Starlette 문서 사이트에서 테스트에서 라이프스팬 실행하기."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)에 대한 자세한 내용을 더 읽을 수 있습니다.
더 이상 권장되지 않는 `startup``shutdown` 이벤트의 경우, 다음과 같이 `TestClient`를 사용할 수 있습니다:
{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *}

View File

@@ -1,13 +1,13 @@
# WebSocket 테스트하기
# WebSocket 테스트하기 { #testing-websockets }
`TestClient`를 사용하여 WebSocket을 테스트할 수 있습니다.
같은 `TestClient`를 사용하여 WebSocket을 테스트할 수 있습니다.
이를 위해 `with` 문에서 `TestClient`를 사용하여 WebSocket에 연결합니다:
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *}
{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *}
/// note | 참고
자세한 내용은 Starlette의 <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank"> WebSocket 테스트</a>에 관한 설명서를 참고하시길 바랍니다.
자세한 내용은 Starlette의 <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">testing WebSockets</a> 문서를 확인하세요.
///

View File

@@ -1,10 +1,10 @@
# `Request` 직접 사용하기
# `Request` 직접 사용하기 { #using-the-request-directly }
지금까지 요청에서 필요한 부분을 각 타입으로 선언하여 사용해 왔습니다.
다음과 같은 곳에서 데이터를 가져왔습니다:
* 경로의 파라미터로부터.
* 경로를 매개변수로.
* 헤더.
* 쿠키.
* 기타 등등.
@@ -13,29 +13,29 @@
하지만 `Request` 객체에 직접 접근해야 하는 상황이 있을 수 있습니다.
## `Request` 객체에 대한 세부 사항
## `Request` 객체에 대한 세부 사항 { #details-about-the-request-object }
**FastAPI**는 실제로 내부에 **Starlette**을 사용하며, 그 위에 여러 도구를 덧붙인 구조입니다. 따라서 여러분이 필요할 때 Starlette의 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> 객체를 직접 사용할 수 있습니다.
`Request` 객체에서 데이터를 직접 가져오는 경우(예: 본문을 읽기)에는 FastAPI가 해당 데이터를 검증하거나 변환하지 않으며, 문서화(OpenAPI를 통한 문서 자동화(로 생성된) API 사용자 인터페이스)도 되지 않니다.
또한 이는 `Request` 객체에서 데이터를 직접 가져오는 경우(예: 본문을 읽기) FastAPI가 해당 데이터를 검증하거나 변환하지 않으며, 문서화(OpenAPI를 통한 자동 API 사용자 인터페이스)도 되지 않는다는 의미이기도 합니다.
그러나 다른 매개변수(예: Pydantic 모델을 사용한 본문)는 여전히 검증, 변환, 주석 추가 등이 이루어집니다.
하지만 특정한 경우에는 `Request` 객체에 직접 접근하는 것이 유용할 수 있습니다.
하지만 특정한 경우에는 `Request` 객체를 가져오는 것이 유용할 수 있습니다.
## `Request` 객체를 직접 사용하기
## `Request` 객체를 직접 사용하기 { #use-the-request-object-directly }
여러분이 클라이언트의 IP 주소/호스트 정보를 *경로 작동 함수* 내부에서 가져와야 한다고 가정해 보겠습니다.
여러분이 클라이언트의 IP 주소/호스트 정보를 *경로 처리 함수* 내부에서 가져와야 한다고 가정해 보겠습니다.
이를 위해서는 요청에 직접 접근해야 합니다.
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *}
*경로 작동 함수* 매개변수를 `Request` 타입으로 선언하면 **FastAPI**가 해당 매개변수에 `Request` 객체를 전달하는 것을 알게 됩니다.
*경로 처리 함수* 매개변수를 `Request` 타입으로 선언하면 **FastAPI**가 해당 매개변수에 `Request`를 전달하는 것을 알게 됩니다.
/// tip | 팁
이 경우, 요청 매개변수와 함께 경로 매개변수를 선언한 것을 볼 수 있습니다.
이 경우, 요청 매개변수 옆에 경로 매개변수를 선언하고 있다는 점을 참고하세요.
따라서, 경로 매개변수는 추출되고 검증되며 지정된 타입으로 변환되고 OpenAPI로 주석이 추가됩니다.
@@ -43,14 +43,14 @@
///
## `Request` 설명서
## `Request` 설명서 { #request-documentation }
여러분은 `Request` 객체에 대한 더 자세한 내용을 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">공식 Starlette 설명서 사이트</a>에서 읽어볼 수 있습니다.
여러분은 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">공식 Starlette 설명서 사이트`Request` 객체</a>에 대한 더 자세한 내용을 읽어볼 수 있습니다.
/// note | 기술 세부사항
`from starlette.requests import Request`를 사용할 수도 있습니다.
**FastAPI**는 여러분(개발자)를 위한 편의를 위해 이를 직접 제공하지만, 실제로는 Starlette에서 가져온 것입니다.
**FastAPI**는 여러분(개발자)를 위한 편의를 위해 이를 직접 제공하지만, Starlette에서 직접 가져온 것입니다.
///

View File

@@ -1,10 +1,10 @@
# WebSockets
# WebSockets { #websockets }
여러분은 **FastAPI**에서 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a>를 사용할 수 있습니다.
## `WebSockets` 설치
## `websockets` 설치 { #install-websockets }
[가상 환경](../virtual-environments.md){.internal-link target=_blank)를 생성하고 활성화한 다음, `websockets`를 설치하세요:
[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, `websockets`("WebSocket" 프로토콜을 쉽게 사용할 수 있게 해주는 Python 라이브러리)를 설치하세요:
<div class="termy">
@@ -16,13 +16,13 @@ $ pip install websockets
</div>
## WebSockets 클라이언트
## WebSockets 클라이언트 { #websockets-client }
### 프로덕션 환경에서
### 프로덕션 환경에서 { #in-production }
여러분의 프로덕션 시스템에서는 React, Vue.js 또는 Angular와 같은 최신 프레임워크로 생성된 프런트엔드를 사용하고 있을 가능성이 높습니다.
백엔드와 WebSockets을 사용해 통신하려면 아마도 프런트엔드의 유틸리티를 사용할 것입니다.
그리고 백엔드와 WebSockets을 사용해 통신하려면 아마도 프런트엔드의 유틸리티를 사용할 것입니다.
또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 응용 프로그램을 가질 수도 있습니다.
@@ -30,23 +30,23 @@ $ pip install websockets
---
하지만 이번 예제에서는 일부 자바스크립트를 포함한 간단한 HTML 문서를 사용하겠습니다. 모든 것을 긴 문자열 안에 넣습니다.
하지만 이번 예제에서는 일부 자바스크립트를 포함한 매우 간단한 HTML 문서를 사용하겠습니다. 모든 것을 긴 문자열 안에 넣습니다.
물론, 이는 최적의 방법이 아니며 프로덕션 환경에서는 사용하지 않을 것입니다.
프로덕션 환경에서는 위에서 설명한 옵션 중 하나를 사용하는 것이 좋습니다.
프로덕션 환경에서는 위에서 설명한 옵션 중 하나를 사용할 것입니다.
그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다:
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *}
## `websocket` 생성하기
## `websocket` 생성하기 { #create-a-websocket }
**FastAPI** 응용 프로그램에서 `websocket`을 생성합니다:
{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *}
/// note | 기술 세부사항
/// note | 기술 세부사항
`from starlette.websockets import WebSocket`을 사용할 수도 있습니다.
@@ -54,17 +54,17 @@ $ pip install websockets
///
## 메시지를 대기하고 전송하기
## 메시지를 대기하고 전송하기 { #await-for-messages-and-send-messages }
WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다.
{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *}
여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다.
## 시도해보기
## 시도해보기 { #try-it }
파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다:
파일 이름이 `main.py`라고 가정하고 다음으로 응용 프로그램을 실행합니다:
<div class="termy">
@@ -76,7 +76,7 @@ $ fastapi dev main.py
</div>
브라우저에서 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>을 열어보세요.
브라우저에서 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>을 세요.
간단한 페이지가 나타날 것입니다:
@@ -86,7 +86,7 @@ $ fastapi dev main.py
<img src="/img/tutorial/websockets/image02.png">
**FastAPI** WebSocket 응용 프로그램이 응답을 돌려줄 것입니다:
그리고 WebSockets가 포함된 **FastAPI** 응용 프로그램이 응답을 돌려줄 것입니다:
<img src="/img/tutorial/websockets/image03.png">
@@ -94,9 +94,9 @@ $ fastapi dev main.py
<img src="/img/tutorial/websockets/image04.png">
모든 메시지는 동일한 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`을 발생시킵니다.
명세서에 정의된 <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">유효한 코드</a>를 사용하여 종료 코드를 설정할 수 있습니다.
///
### 종속성을 가진 WebSockets 테스트
### 종속성을 가진 WebSockets 시도해보기 { #try-the-websockets-with-dependencies }
파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다:
파일 이름이 `main.py`라고 가정하고 다음으로 응용 프로그램을 실행합니다:
<div class="termy">
@@ -133,9 +133,9 @@ $ fastapi dev main.py
</div>
브라우저에서 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>을 열어보세요.
브라우저에서 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>을 세요.
다음과 같은 값을 설정할 수 있습니다:
여기에서 다음을 설정할 수 있습니다:
* 경로에 사용된 "Item ID".
* 쿼리 매개변수로 사용된 "Token".
@@ -146,13 +146,13 @@ $ fastapi dev main.py
///
WebSocket에 연결하고 메시지를 전송 및 수신할 수 있습니다:
렇게 하면 WebSocket에 연결하고 메시지를 전송 및 수신할 수 있습니다:
<img src="/img/tutorial/websockets/image05.png">
## 연결 해제 및 다중 클라이언트 처리
## 연결 해제 및 다중 클라이언트 처리 { #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 등을 지원하는 도구를 찾고 있다면, <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>를 확인하세요.
FastAPI와 쉽게 통합할 수 있으면서 더 견고하고 Redis, PostgreSQL 등을 지원하는 도구가 필요하다면, <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>를 확인하세요.
///
## 추가 정보
## 추가 정보 { #more-info }
다음 옵션에 대한 자세한 내용을 보려면 Starlette의 문서를 확인하세요:
다음 옵션에 대해 더 알아보려면 Starlette의 문서를 확인하세요:
* <a href="https://www.starlette.dev/websockets/" class="external-link" target="_blank">`WebSocket` 클래스</a>.
* <a href="https://www.starlette.dev/endpoints/#websocketendpoint" class="external-link" target="_blank">클래스 기반 WebSocket 처리</a>.

View File

@@ -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!
```
그리고 다음으로 이동하면 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> Flask의 응답을 볼 수 있습니다:
그리고 다음으로 이동하면 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> **FastAPI**의 응답을 볼 수 있습니다:
```JSON
{

View File

@@ -0,0 +1,485 @@
# 대안, 영감, 비교 { #alternatives-inspiration-and-comparisons }
**FastAPI**에 영감을 준 것들, 대안과의 비교, 그리고 그로부터 무엇을 배웠는지에 대한 내용입니다.
## 소개 { #intro }
다른 사람들의 이전 작업이 없었다면 **FastAPI**는 존재하지 않았을 것입니다.
그 전에 만들어진 많은 도구들이 **FastAPI**의 탄생에 영감을 주었습니다.
저는 여러 해 동안 새로운 framework를 만드는 것을 피하고 있었습니다. 먼저 **FastAPI**가 다루는 모든 기능을 여러 서로 다른 framework, plug-in, 도구를 사용해 해결해 보려고 했습니다.
하지만 어느 시점에는, 이전 도구들의 가장 좋은 아이디어를 가져와 가능한 최선의 방식으로 조합하고, 이전에는 존재하지 않았던 언어 기능(Python 3.6+ type hints)을 활용해 이 모든 기능을 제공하는 무언가를 만드는 것 외에는 다른 선택지가 없었습니다.
## 이전 도구들 { #previous-tools }
### <a href="https://www.djangoproject.com/" class="external-link" target="_blank">Django</a> { #django }
가장 인기 있는 Python framework이며 널리 신뢰받고 있습니다. Instagram 같은 시스템을 만드는 데 사용됩니다.
상대적으로 관계형 데이터베이스(예: MySQL 또는 PostgreSQL)와 강하게 결합되어 있어서, NoSQL 데이터베이스(예: Couchbase, MongoDB, Cassandra 등)를 주 저장 엔진으로 사용하는 것은 그리 쉽지 않습니다.
백엔드에서 HTML을 생성하기 위해 만들어졌지, 현대적인 프런트엔드(예: React, Vue.js, Angular)나 다른 시스템(예: <abbr title="Internet of Things - 사물 인터넷">IoT</abbr> 기기)에서 사용되는 API를 만들기 위해 설계된 것은 아닙니다.
### <a href="https://www.django-rest-framework.org/" class="external-link" target="_blank">Django REST Framework</a> { #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 문서화 웹 사용자 인터페이스를 제공하기.
///
### <a href="https://flask.palletsprojects.com" class="external-link" target="_blank">Flask</a> { #flask }
Flask는 "microframework"로, Django에 기본으로 포함된 데이터베이스 통합이나 여러 기능들을 포함하지 않습니다.
이 단순함과 유연성 덕분에 NoSQL 데이터베이스를 주 데이터 저장 시스템으로 사용하는 같은 작업이 가능합니다.
매우 단순하기 때문에 비교적 직관적으로 배울 수 있지만, 문서가 어떤 지점에서는 다소 기술적으로 깊어지기도 합니다.
또한 데이터베이스, 사용자 관리, 혹은 Django에 미리 구축되어 있는 다양한 기능들이 꼭 필요하지 않은 다른 애플리케이션에도 흔히 사용됩니다. 물론 이런 기능들 중 다수는 plug-in으로 추가할 수 있습니다.
이런 구성요소의 분리와, 필요한 것만 정확히 덧붙여 확장할 수 있는 "microframework"라는 점은 제가 유지하고 싶었던 핵심 특성이었습니다.
Flask의 단순함을 고려하면 API를 구축하는 데 잘 맞는 것처럼 보였습니다. 다음으로 찾고자 했던 것은 Flask용 "Django REST Framework"였습니다.
/// check | **FastAPI**에 영감을 준 것
micro-framework가 되기. 필요한 도구와 구성요소를 쉽게 조합할 수 있도록 하기.
단순하고 사용하기 쉬운 routing 시스템을 갖기.
///
### <a href="https://requests.readthedocs.io" class="external-link" target="_blank">Requests</a> { #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)을 직접, 직관적이고 명확한 방식으로 사용하기.
* 합리적인 기본값을 제공하되, 강력한 커스터마이징을 가능하게 하기.
///
### <a href="https://swagger.io/" class="external-link" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" class="external-link" target="_blank">OpenAPI</a> { #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 사양을 위한 열린 표준을 채택하고 사용하기.
또한 표준 기반의 사용자 인터페이스 도구를 통합하기:
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>
* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>
이 두 가지는 꽤 대중적이고 안정적이기 때문에 선택되었습니다. 하지만 간단히 검색해보면 OpenAPI를 위한 대안 UI가 수십 가지나 있다는 것을 알 수 있습니다(**FastAPI**와 함께 사용할 수 있습니다).
///
### Flask REST framework들 { #flask-rest-frameworks }
Flask REST framework는 여러 개가 있지만, 시간을 들여 조사해 본 결과, 상당수가 중단되었거나 방치되어 있었고, 해결되지 않은 여러 이슈 때문에 적합하지 않은 경우가 많았습니다.
### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow }
API 시스템에 필요한 주요 기능 중 하나는 데이터 "<abbr title="also called marshalling, conversion - 마샬링, 변환이라고도 합니다">serialization</abbr>"입니다. 이는 코드(Python)에서 데이터를 가져와 네트워크로 전송할 수 있는 형태로 변환하는 것을 의미합니다. 예를 들어 데이터베이스의 데이터를 담은 객체를 JSON 객체로 변환하거나, `datetime` 객체를 문자열로 변환하는 등의 작업입니다.
API에 또 하나 크게 필요한 기능은 데이터 검증입니다. 특정 파라미터를 기준으로 데이터가 유효한지 확인하는 것입니다. 예를 들어 어떤 필드가 `int`인지, 임의의 문자열이 아닌지 확인하는 식입니다. 이는 특히 들어오는 데이터에 유용합니다.
데이터 검증 시스템이 없다면, 모든 검사를 코드에서 수동으로 해야 합니다.
이런 기능들을 제공하기 위해 Marshmallow가 만들어졌습니다. 훌륭한 라이브러리이며, 저도 이전에 많이 사용했습니다.
하지만 Python type hints가 존재하기 전에 만들어졌습니다. 그래서 각 <abbr title="the definition of how data should be formed - 데이터가 어떻게 구성되어야 하는지에 대한 정의">schema</abbr>를 정의하려면 Marshmallow가 제공하는 특정 유틸리티와 클래스를 사용해야 합니다.
/// check | **FastAPI**에 영감을 준 것
데이터 타입과 검증을 제공하는 "schema"를 코드로 정의하고, 이를 자동으로 활용하기.
///
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs }
API에 필요한 또 다른 큰 기능은 들어오는 요청에서 데이터를 <abbr title="reading and converting to Python data - 읽어서 Python 데이터로 변환하기">parsing</abbr>하는 것입니다.
Webargs는 Flask를 포함한 여러 framework 위에서 이를 제공하기 위해 만들어진 도구입니다.
내부적으로 Marshmallow를 사용해 데이터 검증을 수행합니다. 그리고 같은 개발자들이 만들었습니다.
아주 훌륭한 도구이며, 저도 **FastAPI**를 만들기 전에 많이 사용했습니다.
/// info | 정보
Webargs는 Marshmallow와 같은 개발자들이 만들었습니다.
///
/// check | **FastAPI**에 영감을 준 것
들어오는 요청 데이터의 자동 검증을 갖기.
///
### <a href="https://apispec.readthedocs.io/en/stable/" class="external-link" target="_blank">APISpec</a> { #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를 지원하기.
///
### <a href="https://flask-apispec.readthedocs.io/en/latest/" class="external-link" target="_blank">Flask-apispec</a> { #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입니다:
* <a href="https://github.com/tiangolo/full-stack" class="external-link" target="_blank">https://github.com/tiangolo/full-stack</a>
* <a href="https://github.com/tiangolo/full-stack-flask-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchbase</a>
* <a href="https://github.com/tiangolo/full-stack-flask-couchdb" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchdb</a>
그리고 이 동일한 full-stack generator들이 [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}의 기반이 되었습니다.
/// info | 정보
Flask-apispec은 Marshmallow와 같은 개발자들이 만들었습니다.
///
/// check | **FastAPI**에 영감을 준 것
serialization과 validation을 정의하는 동일한 코드로부터 OpenAPI schema를 자동 생성하기.
///
### <a href="https://nestjs.com/" class="external-link" target="_blank">NestJS</a> (그리고 <a href="https://angular.io/" class="external-link" target="_blank">Angular</a>) { #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 타입을 사용해 뛰어난 에디터 지원을 제공하기.
강력한 의존성 주입 시스템을 갖추기. 코드 반복을 최소화하는 방법을 찾기.
///
### <a href="https://sanic.readthedocs.io/en/latest/" class="external-link" target="_blank">Sanic</a> { #sanic }
`asyncio` 기반의 매우 빠른 Python framework 중 초기 사례였습니다. Flask와 매우 유사하게 만들어졌습니다.
/// note | 기술 세부사항
기본 Python `asyncio` 루프 대신 <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a>를 사용했습니다. 이것이 매우 빠르게 만든 요인입니다.
이는 Uvicorn과 Starlette에 명확히 영감을 주었고, 현재 공개 benchmark에서는 이 둘이 Sanic보다 더 빠릅니다.
///
/// check | **FastAPI**에 영감을 준 것
미친 성능을 낼 수 있는 방법을 찾기.
그래서 **FastAPI**는 Starlette를 기반으로 합니다. Starlette는 사용 가능한 framework 중 가장 빠르기 때문입니다(서드파티 benchmark로 테스트됨).
///
### <a href="https://falconframework.org/" class="external-link" target="_blank">Falcon</a> { #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를 설정하는 데 사용됩니다.
///
### <a href="https://moltenframework.com/" class="external-link" target="_blank">Molten</a> { #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에 이미 포함되어 있습니다).
///
### <a href="https://github.com/hugapi/hug" class="external-link" target="_blank">Hug</a> { #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를 자동으로 정렬하는 훌륭한 도구인 <a href="https://github.com/timothycrosley/isort" class="external-link" target="_blank">`isort`</a>의 제작자이기도 합니다.
///
/// check | **FastAPI**에 영감을 준 아이디어들
Hug는 APIStar의 일부에 영감을 주었고, 저는 APIStar와 함께 Hug를 가장 유망한 도구 중 하나로 보았습니다.
Hug는 Python type hints로 파라미터를 선언하고, API를 정의하는 schema를 자동으로 생성하도록 **FastAPI**에 영감을 주었습니다.
Hug는 헤더와 쿠키를 설정하기 위해 함수에 `response` 파라미터를 선언하도록 **FastAPI**에 영감을 주었습니다.
///
### <a href="https://github.com/encode/apistar" class="external-link" target="_blank">APIStar</a> (<= 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 }
### <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> { #pydantic }
Pydantic은 Python type hints를 기반으로 데이터 검증, serialization, 문서화(JSON Schema 사용)를 정의하는 라이브러리입니다.
그 덕분에 매우 직관적입니다.
Marshmallow와 비교할 수 있습니다. 다만 benchmark에서 Marshmallow보다 빠릅니다. 그리고 동일한 Python type hints를 기반으로 하므로 에디터 지원도 훌륭합니다.
/// check | **FastAPI**가 이를 사용하는 목적
모든 데이터 검증, 데이터 serialization, 자동 모델 문서화(JSON Schema 기반)를 처리하기.
그 다음 **FastAPI**는 그 JSON Schema 데이터를 가져와 OpenAPI에 포함시키며, 그 외에도 여러 작업을 수행합니다.
///
### <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> { #starlette }
Starlette는 경량 <abbr title="The new standard for building asynchronous Python web applications - 비동기 Python 웹 애플리케이션을 구축하기 위한 새로운 표준">ASGI</abbr> 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에 강력한 기능을 더한 것입니다.
///
### <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a> { #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} 섹션을 확인하세요.

View File

@@ -1,10 +1,10 @@
# 벤치마크
# 벤치마크 { #benchmarks }
독립적인 TechEmpower 벤치마크에 따르면 **FastAPI** 애플리케이션이 Uvicorn을 사용하여 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">가장 빠른 Python 프레임워크 중 하나</a>로 실행되며, Starlette와 Uvicorn 자체(내부적으로 FastAPI가 사용하는 도구)보다 조금 아래에 위치합니다.
독립적인 TechEmpower 벤치마크에 따르면 **FastAPI** 애플리케이션이 Uvicorn을 사용하여 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">사용 가능한 가장 빠른 Python 프레임워크 중 하나</a>로 실행되며, 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 등 데이터 검증, 직렬화 및 문서화를 제공하는 웹 애플리케이션 프레임워크(또는 도구 집합)와 비교하세요. 통합된 자동 데이터 검증, 직렬화 및 문서화를 제공하는 프레임워크입니다.

View File

@@ -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 **생태계**가 지속적이고 건전한 **발전**을 할 수 있습니다.
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**는 **FastAPI**를 만든 동일한 작성자와 팀이 구축했습니다.
이는 FastAPI **커뮤니티** (여러분)에 대한 진정한 헌신을 보여줍니다. 그들은 여러분에게 **좋은 서비스**를 제공할 뿐 만이 아니라 여러분이 **훌륭하고 건강한 프레임워크인** FastAPI 를 사용하길 원하기 때문입니다. 🙇
최소한의 노력으로 API **구축**, **배포**, **접근**하는 과정을 간소화합니다.
아래와 같은 서비스를 사용해보고 각 서비스의 가이드를 따를 수도 있습니다.
FastAPI로 앱을 빌드할 때의 동일한 **개발자 경험**을 클라우드에 **배포**하는 데에도 제공합니다. 🎉
FastAPI Cloud는 *FastAPI and friends* 오픈 소스 프로젝트의 주요 후원자이자 자금 제공자입니다. ✨
## 클라우드 제공업체 - 후원자들 { #cloud-providers-sponsors }
다른 몇몇 클라우드 제공업체들도 ✨ [**FastAPI를 후원합니다**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨. 🙇
가이드를 따라 하고 서비스를 사용해보기 위해 이들도 고려해볼 수 있습니다:
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a>
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a>

View File

@@ -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 등)을 실행하는 프로세스가 여러 개 있는 것도 보일 가능성이 큽니다. 보통 탭마다 하나의 프로세스를 실행하고, 그 외에도 추가 프로세스 몇 개가 더 있습니다.
<img class="shadow" src="/img/deployment/concepts/image01.png">
---
이제 **프로세스**와 **프로그램**의 차이를 알았으니, 배포에 대한 이야기를 계속해 보겠습니다.
## 시작 시 실행 { #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에 변수로 로드한 모든 내용을 담습니다.
<img src="/img/deployment/concepts/process-ram.drawio.svg">
그리고 물론 같은 머신에는 애플리케이션 외에도 **다른 프로세스**들이 실행 중일 가능성이 큽니다.
흥미로운 점은 각 프로세스의 **CPU 사용률**은 시간에 따라 크게 **변동**할 수 있지만, **메모리(RAM)**는 보통 대체로 **안정적**으로 유지된다는 것입니다.
매번 비슷한 양의 연산을 수행하는 API이고 클라이언트가 많다면, **CPU 사용률**도 (급격히 오르내리기보다는) *안정적일* 가능성이 큽니다.
### 복제 도구와 전략 예시 { #examples-of-replication-tools-and-strategies }
이를 달성하는 접근 방식은 여러 가지가 있을 수 있으며, 다음 장들에서 Docker와 컨테이너를 설명할 때 구체적인 전략을 더 알려드리겠습니다.
고려해야 할 주요 제약은 **공개 IP**의 **포트**를 처리하는 **단일** 컴포넌트가 있어야 한다는 점입니다. 그리고 그 컴포넌트는 복제된 **프로세스/워커**로 통신을 **전달**할 방법이 있어야 합니다.
가능한 조합과 전략 몇 가지는 다음과 같습니다:
* `--workers` 옵션을 사용한 **Uvicorn**
* 하나의 Uvicorn **프로세스 매니저**가 **IP**와 **포트**에서 리스닝하고, **여러 Uvicorn 워커 프로세스**를 시작합니다.
* **Kubernetes** 및 기타 분산 **컨테이너 시스템**
* **Kubernetes** 레이어의 무언가가 **IP**와 **포트**에서 리스닝합니다. 그리고 **여러 컨테이너**를 두어 복제하며, 각 컨테이너에는 **하나의 Uvicorn 프로세스**가 실행됩니다.
* 이를 대신 처리해주는 **클라우드 서비스**
* 클라우드 서비스가 **복제를 대신 처리**해줄 가능성이 큽니다. 실행할 **프로세스**나 사용할 **컨테이너 이미지**를 정의하게 해줄 수도 있지만, 어떤 경우든 대개 **단일 Uvicorn 프로세스**를 기준으로 하고, 클라우드 서비스가 이를 복제하는 역할을 맡습니다.
/// tip | 팁
**컨테이너**, Docker, Kubernetes에 대한 일부 내용이 아직은 잘 이해되지 않아도 괜찮습니다.
다음 장에서 컨테이너 이미지, Docker, Kubernetes 등을 더 설명하겠습니다: [컨테이너에서 FastAPI - Docker](docker.md){.internal-link target=_blank}.
///
## 시작 전 사전 단계 { #previous-steps-before-starting }
애플리케이션을 **시작하기 전에** 어떤 단계를 수행하고 싶은 경우가 많습니다.
예를 들어 **데이터베이스 마이그레이션**을 실행하고 싶을 수 있습니다.
하지만 대부분의 경우, 이런 단계는 **한 번만** 수행하고 싶을 것입니다.
그래서 애플리케이션을 시작하기 전에 그 **사전 단계**를 수행할 **단일 프로세스**를 두고 싶을 것입니다.
또한 이후에 애플리케이션 자체를 **여러 프로세스**(여러 워커)로 시작하더라도, 사전 단계를 수행하는 프로세스는 *반드시* 하나만 실행되도록 해야 합니다. 만약 사전 단계를 **여러 프로세스**가 수행하면, **병렬로** 실행하면서 작업이 **중복**될 수 있습니다. 그리고 데이터베이스 마이그레이션처럼 민감한 작업이라면 서로 충돌을 일으킬 수 있습니다.
물론 사전 단계를 여러 번 실행해도 문제가 없는 경우도 있습니다. 그런 경우에는 처리하기가 훨씬 쉽습니다.
/// tip | 팁
또한 설정에 따라, 어떤 경우에는 애플리케이션을 시작하기 전에 **사전 단계가 전혀 필요 없을** 수도 있다는 점을 기억하세요.
그런 경우에는 이런 것들을 전혀 걱정할 필요가 없습니다. 🤷
///
### 사전 단계 전략 예시 { #examples-of-previous-steps-strategies }
이는 여러분이 **시스템을 배포하는 방식**에 크게 좌우되며, 프로그램을 시작하는 방식, 재시작 처리 방식 등과도 연결되어 있을 가능성이 큽니다.
가능한 아이디어는 다음과 같습니다:
* 앱 컨테이너보다 먼저 실행되는 Kubernetes의 “Init Container”
* 사전 단계를 실행한 다음 애플리케이션을 시작하는 bash 스크립트
* 이 bash 스크립트를 시작/재시작하고, 오류를 감지하는 등의 방법도 여전히 필요합니다.
/// tip | 팁
컨테이너로 이를 처리하는 더 구체적인 예시는 다음 장에서 제공하겠습니다: [컨테이너에서 FastAPI - Docker](docker.md){.internal-link target=_blank}.
///
## 리소스 활용 { #resource-utilization }
서버는 여러분이 프로그램으로 소비하거나 **활용(utilize)**할 수 있는 **리소스**입니다. CPU의 계산 시간과 사용 가능한 RAM 메모리가 대표적입니다.
시스템 리소스를 얼마나 소비/활용하고 싶으신가요? “많지 않게”라고 생각하기 쉽지만, 실제로는 **크래시하지 않는 선에서 가능한 한 많이** 사용하고 싶을 가능성이 큽니다.
서버 3대를 비용을 내고 쓰고 있는데 RAM과 CPU를 조금만 사용한다면, 아마 **돈을 낭비**하고 💸, **서버 전력도 낭비**하고 🌎, 기타 등등이 될 수 있습니다.
그 경우에는 서버를 2대만 두고, 각 서버의 리소스(CPU, 메모리, 디스크, 네트워크 대역폭 등)를 더 높은 비율로 사용하는 것이 더 나을 수 있습니다.
반대로 서버 2대를 두고 CPU와 RAM을 **100%** 사용하고 있다면, 어느 시점에 프로세스 하나가 더 많은 메모리를 요청하게 되고, 서버는 디스크를 “메모리”처럼 사용해야 할 수도 있습니다(수천 배 느릴 수 있습니다). 또는 심지어 **크래시**할 수도 있습니다. 혹은 어떤 프로세스가 계산을 해야 하는데 CPU가 다시 비워질 때까지 기다려야 할 수도 있습니다.
이 경우에는 **서버 한 대를 추가**로 확보하고 일부 프로세스를 그쪽에서 실행해, 모두가 **충분한 RAM과 CPU 시간**을 갖도록 하는 편이 더 낫습니다.
또 어떤 이유로 API 사용량이 **급증(spike)**할 가능성도 있습니다. 바이럴이 되었거나, 다른 서비스나 봇이 사용하기 시작했을 수도 있습니다. 그런 경우를 대비해 추가 리소스를 확보해두고 싶을 수 있습니다.
리소스 활용률 목표로 **임의의 수치**를 정할 수 있습니다. 예를 들어 **50%에서 90% 사이**처럼요. 요점은, 이런 것들이 배포를 조정할 때 측정하고 튜닝하는 주요 지표가 될 가능성이 크다는 것입니다.
`htop` 같은 간단한 도구로 서버의 CPU와 RAM 사용량, 또는 각 프로세스별 사용량을 볼 수 있습니다. 혹은 서버 여러 대에 분산될 수도 있는 더 복잡한 모니터링 도구를 사용할 수도 있습니다.
## 요약 { #recap }
여기까지 애플리케이션 배포 방식을 결정할 때 염두에 두어야 할 주요 개념들을 읽었습니다:
* 보안 - HTTPS
* 시작 시 실행
* 재시작
* 복제(실행 중인 프로세스 수)
* 메모리
* 시작 전 사전 단계
이 아이디어들을 이해하고 적용하는 방법을 알면, 배포를 구성하고 조정할 때 필요한 직관을 얻는 데 도움이 될 것입니다. 🤓
다음 섹션에서는 따라 할 수 있는 가능한 전략의 더 구체적인 예시를 제공하겠습니다. 🚀

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