Compare commits

..

81 Commits

Author SHA1 Message Date
Sebastián Ramírez
63e7edb295 🔖 Release version 0.101.1 2023-08-14 12:03:14 +02:00
Sebastián Ramírez
50b6ff7da6 📝 Update release notes 2023-08-14 12:02:43 +02:00
github-actions
d46cd0b1f0 📝 Update release notes 2023-08-14 09:50:40 +00:00
Sebastián Ramírez
5e8f7f13d7 Add ResponseValidationError printable details, to show up in server error logs (#10078) 2023-08-14 11:49:57 +02:00
github-actions
dafaf6a34c 📝 Update release notes 2023-08-14 09:17:05 +00:00
github-actions
87cc40e483 📝 Update release notes 2023-08-14 09:15:26 +00:00
Yusuke Tamura
25059a7717 🌐 Add Japanese translation for docs/ja/docs/deployment/concepts.md (#10062)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-14 11:14:37 +02:00
Yusuke Tamura
2a5cc5fff3 🌐 Add Japanese translation for docs/ja/docs/deployment/server-workers.md (#10064)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-14 11:13:28 +02:00
github-actions
014262c203 📝 Update release notes 2023-08-14 09:13:05 +00:00
Yusuke Tamura
9d6ce823c1 🌐 Update Japanese translation for docs/ja/docs/deployment/docker.md (#10073)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-14 11:12:14 +02:00
github-actions
48d203a1e7 📝 Update release notes 2023-08-14 09:10:51 +00:00
Rostyslav
47166ed56c 🌐 Add Ukrainian translation for docs/uk/docs/fastapi-people.md (#10059) 2023-08-14 11:10:06 +02:00
github-actions
78f38c6bfd 📝 Update release notes 2023-08-10 15:59:15 +00:00
Rostyslav
e6afc5911b 🌐 Add Ukrainian translation for docs/uk/docs/tutorial/cookie-params.md (#10032) 2023-08-10 17:58:13 +02:00
github-actions
5c2a155809 📝 Update release notes 2023-08-10 15:55:32 +00:00
Vladislav Kramorenko
fe3eaf63e6 🌐 Add Russian translation for docs/ru/docs/deployment/docker.md (#9971)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Yois4101 <119609381+Yois4101@users.noreply.github.com>
2023-08-10 17:53:26 +02:00
github-actions
1f0d9086b3 📝 Update release notes 2023-08-10 15:53:06 +00:00
Nguyễn Khắc Thành
f0ab797de4 🌐 Add Vietnamese translation for docs/vi/docs/python-types.md (#10047)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-10 17:52:25 +02:00
github-actions
82fafcc7ea 📝 Update release notes 2023-08-09 17:05:31 +00:00
Sebastián Ramírez
87398723f9 🔧 Add sponsor Porter (#10051) 2023-08-09 19:04:49 +02:00
github-actions
01383a57cb 📝 Update release notes 2023-08-09 13:27:14 +00:00
Sebastián Ramírez
14c96ef31b 🔧 Update sponsors, add Jina back as bronze sponsor (#10050) 2023-08-09 15:26:33 +02:00
github-actions
942ee69d85 📝 Update release notes 2023-08-05 08:46:58 +00:00
github-actions
6df10c9753 📝 Update release notes 2023-08-05 08:44:36 +00:00
github-actions
0f4a962c20 📝 Update release notes 2023-08-05 08:43:01 +00:00
Russ Biggs
bdd991244d ✏️ Fix typo in deprecation warnings in fastapi/params.py (#9854)
fix typo for deprecation warnings depreacated -> deprecated
2023-08-05 10:41:21 +02:00
Francis Bergin
69d5ebf34d ✏️ Fix typo in release notes (#9835) 2023-08-05 10:40:24 +02:00
ElliottLarsen
5e59acd35b ✏️ Fix typos in comments on internal code in fastapi/concurrency.py and fastapi/routing.py (#9590)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
2023-08-05 10:39:38 +02:00
github-actions
bb7bbafb5f 📝 Update release notes 2023-08-05 08:38:32 +00:00
github-actions
87e126be2e 📝 Update release notes 2023-08-05 08:36:27 +00:00
Adejumo Ridwan Suleiman
33e77b6e25 📝 Add external article: Build an SMS Spam Classifier Serverless Database with FaunaDB and FastAPI (#9847) 2023-08-05 10:36:05 +02:00
github-actions
51f5497f3f 📝 Update release notes 2023-08-05 08:35:55 +00:00
Vicente Merino
0b496ea1f8 📝 Fix typo in docs/en/docs/contributing.md (#9878)
Co-authored-by: Vicente Merino <vmerino@rexmas.com>
2023-08-05 10:34:07 +02:00
Reza Rohani
b76112f1a5 📝 Fix code highlighting in docs/en/docs/tutorial/bigger-applications.md (#9806)
Update bigger-applications.md
2023-08-05 10:33:08 +02:00
github-actions
f2e80fae09 📝 Update release notes 2023-08-05 08:28:26 +00:00
github-actions
d86a695db9 📝 Update release notes 2023-08-05 08:26:40 +00:00
Aleksandr Pavlov
1c919dee3c 🌐 Add Russian translation for docs/ru/docs/tutorial/dependencies/global-dependencies.md (#9970)
Co-authored-by: dedkot <dedkot01@gmail.com>
Co-authored-by: Vladislav Kramorenko <85196001+Xewus@users.noreply.github.com>
2023-08-05 10:26:03 +02:00
Ahsan Sheraz
5891be5ff1 🌐 Add Urdu translation for docs/ur/docs/benchmarks.md (#9974) 2023-08-05 10:24:21 +02:00
github-actions
abfcb59fd0 📝 Update release notes 2023-08-05 08:23:39 +00:00
github-actions
0148c9508c 📝 Update release notes 2023-08-05 08:23:14 +00:00
dependabot[bot]
8f316be088 ⬆ Bump mypy from 1.4.0 to 1.4.1 (#9756)
Bumps [mypy](https://github.com/python/mypy) from 1.4.0 to 1.4.1.
- [Commits](https://github.com/python/mypy/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 10:22:58 +02:00
dependabot[bot]
d48a184dd8 ⬆ Bump mkdocs-material from 9.1.17 to 9.1.21 (#9960)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.17 to 9.1.21.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.17...9.1.21)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-05 10:22:39 +02:00
Sebastián Ramírez
8adbafc076 🔖 Release version 0.101.0 2023-08-04 23:00:17 +02:00
Sebastián Ramírez
4b5277744a 📝 Update release notes 2023-08-04 22:59:44 +02:00
github-actions
89a7cea561 📝 Update release notes 2023-08-04 20:58:08 +00:00
Sebastián Ramírez
77d1f69b1f 📌 Do not allow Pydantic 2.1.0 that breaks (require 2.1.1) (#10012) 2023-08-04 22:57:30 +02:00
Sebastián Ramírez
944c591803 📝 Update release notes 2023-08-04 22:50:34 +02:00
github-actions
1c20514738 📝 Update release notes 2023-08-04 20:47:42 +00:00
Sebastián Ramírez
19a2c3bb54 Enable Pydantic's serialization mode for responses, add support for Pydantic's computed_field, better OpenAPI for response models, proper required attributes, better generated clients (#10011)
*  Enable Pydantic's serialization mode for responses

*  Update tests with new Pydantic v2 serialization mode

*  Add a test for Pydantic v2's computed_field
2023-08-04 22:47:07 +02:00
github-actions
d943e02232 📝 Update release notes 2023-08-04 18:19:22 +00:00
Sebastián Ramírez
ebdf952545 👷 Add GitHub Actions step dump context to debug external failures (#10008) 2023-08-04 20:18:38 +02:00
github-actions
b3a1f91004 📝 Update release notes 2023-08-04 17:48:24 +00:00
Sebastián Ramírez
86e4e9f8f9 🔧 Restore MkDocs Material pin after the fix (#10001) 2023-08-04 19:47:42 +02:00
github-actions
3af7265a43 📝 Update release notes 2023-08-03 16:00:19 +00:00
Sebastián Ramírez
059fb12892 🔧 Update the Question template to ask for the Pydantic version (#10000) 2023-08-03 17:59:41 +02:00
github-actions
10b4c31f06 📝 Update release notes 2023-08-03 15:47:35 +00:00
David Montague
25694f5ae1 Fix tests for compatibility with pydantic 2.1.1 (#9943)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2023-08-03 17:46:57 +02:00
github-actions
94c48cfc8c 📝 Update release notes 2023-08-03 15:25:10 +00:00
Sebastián Ramírez
4ab0363ad7 Remove direct dependency on MkDocs, Material for MkDocs defines its own dependency (#9986) 2023-08-03 17:24:31 +02:00
github-actions
a73cdaed35 📝 Update release notes 2023-08-03 14:25:48 +00:00
Sebastián Ramírez
3fa6cfbcc5 👥 Update FastAPI People (#9999)
Co-authored-by: github-actions <github-actions@github.com>
2023-08-03 16:25:11 +02:00
github-actions
ad1d7f539e 📝 Update release notes 2023-08-03 14:13:59 +00:00
Sebastián Ramírez
89537a0497 🐳 Update Dockerfile with compatibility versions, to upgrade later (#9998) 2023-08-03 16:12:28 +02:00
github-actions
3829129245 📝 Update release notes 2023-08-02 18:58:29 +00:00
Sebastián Ramírez
53220b9832 Add pydantic-settings to FastAPI People dependencies (#9988) 2023-08-02 20:57:48 +02:00
github-actions
165f29fe5e 📝 Update release notes 2023-08-02 15:57:57 +00:00
Sebastián Ramírez
1e6bfa1f39 ♻️ Update FastAPI People logic with new Pydantic (#9985) 2023-08-02 17:57:20 +02:00
github-actions
b473cdd88d 📝 Update release notes 2023-08-02 15:29:13 +00:00
Irfanuddin Shafi Ahmed
37818f553d Fix test error in Windows for jsonable_encoder (#9840)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
2023-08-02 17:28:34 +02:00
github-actions
2d8a776836 📝 Update release notes 2023-08-02 15:15:10 +00:00
Nikita
88d96799b1 🌐 Add Russian translation for docs/ru/docs/tutorial/security/index.md (#9963)
Co-authored-by: dedkot <dedkot01@gmail.com>
Co-authored-by: Vladislav Kramorenko <85196001+Xewus@users.noreply.github.com>
2023-08-02 17:14:19 +02:00
github-actions
01f91fdb57 📝 Update release notes 2023-08-01 21:40:00 +00:00
Sebastián Ramírez
c2a33f1087 🍱 Update sponsors, Fern badge (#9982) 2023-08-01 23:39:22 +02:00
github-actions
6c8c3b788b 📝 Update release notes 2023-08-01 09:20:23 +00:00
Sebastián Ramírez
d2169fbad9 👷 Deploy docs to Cloudflare Pages (#9978) 2023-08-01 11:19:44 +02:00
github-actions
74de15d0df 📝 Update release notes 2023-07-31 21:49:56 +00:00
Sebastián Ramírez
1da0a7afbd 🔧 Update sponsor Fern (#9979) 2023-07-31 23:49:19 +02:00
github-actions
d38e86ef20 📝 Update release notes 2023-07-31 19:54:46 +00:00
Sebastián Ramírez
a0b987224a 👷 Update CI debug mode with Tmate (#9977) 2023-07-31 21:54:07 +02:00
github-actions
cd6d75e451 📝 Update release notes 2023-07-28 12:16:16 +00:00
Sebastián Ramírez
076bdea671 🌐 Remove Vietnamese note about missing translation (#9957) 2023-07-28 14:15:29 +02:00
83 changed files with 4689 additions and 543 deletions

View File

@@ -123,6 +123,20 @@ body:
```
validations:
required: true
- type: input
id: pydantic-version
attributes:
label: Pydantic Version
description: |
What Pydantic version are you using?
You can find the Pydantic version with:
```bash
python -c "import pydantic; print(pydantic.version.VERSION)"
```
validations:
required: true
- type: input
id: python-version
attributes:

View File

@@ -1,6 +1,6 @@
FROM python:3.7
FROM python:3.9
RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0"
RUN pip install httpx PyGithub "pydantic==2.0.2" pydantic-settings "pyyaml>=5.3.1,<6.0.0"
COPY ./app /app

View File

@@ -9,7 +9,8 @@ from typing import Any, Container, DefaultDict, Dict, List, Set, Union
import httpx
import yaml
from github import Github
from pydantic import BaseModel, BaseSettings, SecretStr
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql"
questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0"
@@ -382,6 +383,7 @@ def get_graphql_response(
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}, category_id: {category_id}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
@@ -389,7 +391,7 @@ def get_graphql_response(
def get_graphql_issue_edges(*, settings: Settings, after: Union[str, None] = None):
data = get_graphql_response(settings=settings, query=issues_query, after=after)
graphql_response = IssuesResponse.parse_obj(data)
graphql_response = IssuesResponse.model_validate(data)
return graphql_response.data.repository.issues.edges
@@ -404,19 +406,19 @@ def get_graphql_question_discussion_edges(
after=after,
category_id=questions_category_id,
)
graphql_response = DiscussionsResponse.parse_obj(data)
graphql_response = DiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.edges
def get_graphql_pr_edges(*, settings: Settings, after: Union[str, None] = None):
data = get_graphql_response(settings=settings, query=prs_query, after=after)
graphql_response = PRsResponse.parse_obj(data)
graphql_response = PRsResponse.model_validate(data)
return graphql_response.data.repository.pullRequests.edges
def get_graphql_sponsor_edges(*, settings: Settings, after: Union[str, None] = None):
data = get_graphql_response(settings=settings, query=sponsors_query, after=after)
graphql_response = SponsorsResponse.parse_obj(data)
graphql_response = SponsorsResponse.model_validate(data)
return graphql_response.data.user.sponsorshipsAsMaintainer.edges
@@ -607,7 +609,7 @@ def get_top_users(
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.json()}")
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.input_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
question_commentors, question_last_month_commentors, question_authors = get_experts(

View File

@@ -44,7 +44,7 @@ jobs:
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v05
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v06
- name: Install docs extras
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-docs.txt
@@ -80,7 +80,7 @@ jobs:
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v05
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v06
- name: Install docs extras
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-docs.txt

View File

@@ -29,21 +29,20 @@ jobs:
run_id: ${{ github.event.workflow_run.id }}
name: docs-site
path: ./site/
- name: Deploy to Netlify
- name: Deploy to Cloudflare Pages
if: steps.download.outputs.found_artifact == 'true'
id: netlify
uses: nwtgck/actions-netlify@v2.0.0
id: deploy
uses: cloudflare/pages-action@v1
with:
publish-dir: './site'
production-deploy: ${{ github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' }}
github-token: ${{ secrets.FASTAPI_PREVIEW_DOCS_NETLIFY }}
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: fastapitiangolo
directory: './site'
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
- name: Comment Deploy
if: steps.netlify.outputs.deploy-url != ''
if: steps.deploy.outputs.url != ''
uses: ./.github/actions/comment-docs-preview-in-pr
with:
token: ${{ secrets.FASTAPI_PREVIEW_DOCS_COMMENT_DEPLOY }}
deploy_url: "${{ steps.netlify.outputs.deploy-url }}"
deploy_url: "${{ steps.deploy.outputs.url }}"

View File

@@ -19,6 +19,10 @@ jobs:
if: github.repository_owner == 'tiangolo'
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: tiangolo/issue-manager@0.4.0
with:
token: ${{ secrets.FASTAPI_ISSUE_MANAGER }}

View File

@@ -9,6 +9,10 @@ jobs:
if: github.repository_owner == 'tiangolo'
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: docker://tiangolo/label-approved:0.0.2
with:
token: ${{ secrets.FASTAPI_LABEL_APPROVED }}

View File

@@ -14,20 +14,24 @@ on:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
latest-changes:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v3
with:
# To allow latest-changes to commit to master
# To allow latest-changes to commit to the main branch
token: ${{ secrets.FASTAPI_LATEST_CHANGES }}
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: docker://tiangolo/latest-changes:0.0.3

View File

@@ -5,16 +5,29 @@ on:
types:
- labeled
- closed
workflow_dispatch:
inputs:
number:
description: PR number
required: true
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: 'false'
jobs:
notify-translations:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v3
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: ./.github/actions/notify-translations

View File

@@ -8,13 +8,17 @@ on:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
fastapi-people:
if: github.repository_owner == 'tiangolo'
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v3
# Ref: https://github.com/actions/runner/issues/2033
- name: Fix git safe.directory in container
@@ -22,7 +26,7 @@ jobs:
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: ./.github/actions/people

View File

@@ -14,6 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/setup-python@v4
with:
python-version: '3.9'

View File

@@ -13,6 +13,10 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
@@ -25,7 +29,7 @@ jobs:
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-pydantic-v2-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-pydantic-v2-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v04
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-tests.txt
@@ -42,6 +46,10 @@ jobs:
pydantic-version: ["pydantic-v1", "pydantic-v2"]
fail-fast: false
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
@@ -54,7 +62,7 @@ jobs:
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ matrix.pydantic-version }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ matrix.pydantic-version }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v04
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-tests.txt
@@ -80,6 +88,10 @@ jobs:
needs: [test]
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
@@ -110,6 +122,10 @@ jobs:
- coverage-combine
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:

View File

@@ -48,7 +48,7 @@ The key features are:
<a href="https://cryptapi.io/" target="_blank" title="CryptAPI: Your easy to use, secure and privacy oriented payment gateway."><img src="https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg"></a>
<a href="https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" target="_blank" title="Build, run and scale your apps on a modern, reliable, and secure PaaS."><img src="https://fastapi.tiangolo.com/img/sponsors/platform-sh.png"></a>
<a href="https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=main-badge" target="_blank" title="Fern | SDKs and API docs"><img src="https://fastapi.tiangolo.com/img/sponsors/fern.png"></a>
<a href="https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=main-badge" target="_blank" title="Fern | SDKs and API docs"><img src="https://fastapi.tiangolo.com/img/sponsors/fern.svg"></a>
<a href="https://www.deta.sh/?ref=fastapi" target="_blank" title="The launchpad for all your (team's) ideas"><img src="https://fastapi.tiangolo.com/img/sponsors/deta.svg"></a>
<a href="https://training.talkpython.fm/fastapi-courses" target="_blank" title="FastAPI video courses on demand from people you trust"><img src="https://fastapi.tiangolo.com/img/sponsors/talkpython.png"></a>
<a href="https://testdriven.io/courses/tdd-fastapi/" target="_blank" title="Learn to build high-quality web apps with best practices"><img src="https://fastapi.tiangolo.com/img/sponsors/testdriven.svg"></a>

View File

@@ -1,5 +1,9 @@
articles:
english:
- author: Adejumo Ridwan Suleiman
author_link: https://www.linkedin.com/in/adejumoridwan/
link: https://medium.com/python-in-plain-english/build-an-sms-spam-classifier-serverless-database-with-faunadb-and-fastapi-23dbb275bc5b
title: Build an SMS Spam Classifier Serverless Database with FaunaDB and FastAPI
- author: Raf Rasenberg
author_link: https://rafrasenberg.com/about/
link: https://rafrasenberg.com/fastapi-lambda/

View File

@@ -2,6 +2,9 @@ sponsors:
- - login: cryptapi
avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
url: https://github.com/cryptapi
- login: fern-api
avatarUrl: https://avatars.githubusercontent.com/u/102944815?v=4
url: https://github.com/fern-api
- login: nanram22
avatarUrl: https://avatars.githubusercontent.com/u/116367316?v=4
url: https://github.com/nanram22
@@ -29,15 +32,18 @@ sponsors:
- login: VincentParedes
avatarUrl: https://avatars.githubusercontent.com/u/103889729?v=4
url: https://github.com/VincentParedes
- - login: arcticfly
avatarUrl: https://avatars.githubusercontent.com/u/41524992?u=03c88529a86cf51f7a380e890d84d84c71468848&v=4
url: https://github.com/arcticfly
- - login: getsentry
avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4
url: https://github.com/getsentry
- - login: takashi-yoneya
- - login: acsone
avatarUrl: https://avatars.githubusercontent.com/u/7601056?v=4
url: https://github.com/acsone
- login: takashi-yoneya
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
url: https://github.com/takashi-yoneya
- login: mercedes-benz
avatarUrl: https://avatars.githubusercontent.com/u/34240465?v=4
url: https://github.com/mercedes-benz
- login: xoflare
avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4
url: https://github.com/xoflare
@@ -50,12 +56,12 @@ sponsors:
- login: BoostryJP
avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
url: https://github.com/BoostryJP
- login: jina-ai
avatarUrl: https://avatars.githubusercontent.com/u/60539444?v=4
url: https://github.com/jina-ai
- - login: HiredScore
avatarUrl: https://avatars.githubusercontent.com/u/3908850?v=4
url: https://github.com/HiredScore
- login: petebachant
avatarUrl: https://avatars.githubusercontent.com/u/4604869?u=b17a5a4ac82f77b7efff864d439e8068d2a36593&v=4
url: https://github.com/petebachant
- login: Trivie
avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4
url: https://github.com/Trivie
@@ -74,9 +80,6 @@ sponsors:
- login: RodneyU215
avatarUrl: https://avatars.githubusercontent.com/u/3329665?u=ec6a9adf8e7e8e306eed7d49687c398608d1604f&v=4
url: https://github.com/RodneyU215
- login: tizz98
avatarUrl: https://avatars.githubusercontent.com/u/5739698?u=f095a3659e3a8e7c69ccd822696990b521ea25f9&v=4
url: https://github.com/tizz98
- login: americanair
avatarUrl: https://avatars.githubusercontent.com/u/12281813?v=4
url: https://github.com/americanair
@@ -92,11 +95,17 @@ sponsors:
- - login: indeedeng
avatarUrl: https://avatars.githubusercontent.com/u/2905043?v=4
url: https://github.com/indeedeng
- login: iguit0
avatarUrl: https://avatars.githubusercontent.com/u/12905770?u=63a1a96d1e6c27d85c4f946b84836599de047f65&v=4
url: https://github.com/iguit0
- login: JacobKochems
avatarUrl: https://avatars.githubusercontent.com/u/41692189?u=a75f62ddc0d060ee6233a91e19c433d2687b8eb6&v=4
url: https://github.com/JacobKochems
- - login: Kludex
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: samuelcolvin
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4
url: https://github.com/samuelcolvin
- login: jefftriplett
avatarUrl: https://avatars.githubusercontent.com/u/50527?u=af1ddfd50f6afd6d99f333ba2ac8d0a5b245ea74&v=4
@@ -104,9 +113,6 @@ sponsors:
- login: jstanden
avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4
url: https://github.com/jstanden
- login: kamalgill
avatarUrl: https://avatars.githubusercontent.com/u/133923?u=0df9181d97436ce330e9acf90ab8a54b7022efe7&v=4
url: https://github.com/kamalgill
- login: dekoza
avatarUrl: https://avatars.githubusercontent.com/u/210980?u=c03c78a8ae1039b500dfe343665536ebc51979b2&v=4
url: https://github.com/dekoza
@@ -149,6 +155,9 @@ sponsors:
- login: zsinx6
avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4
url: https://github.com/zsinx6
- login: kennywakeland
avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4
url: https://github.com/kennywakeland
- login: aacayaco
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
url: https://github.com/aacayaco
@@ -194,9 +203,6 @@ sponsors:
- login: Shackelford-Arden
avatarUrl: https://avatars.githubusercontent.com/u/7362263?v=4
url: https://github.com/Shackelford-Arden
- login: savannahostrowski
avatarUrl: https://avatars.githubusercontent.com/u/8949415?u=c3177aa099fb2b8c36aeba349278b77f9a8df211&v=4
url: https://github.com/savannahostrowski
- login: wdwinslow
avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
url: https://github.com/wdwinslow
@@ -236,9 +242,6 @@ sponsors:
- login: ygorpontelo
avatarUrl: https://avatars.githubusercontent.com/u/32963605?u=35f7103f9c4c4c2589ae5737ee882e9375ef072e&v=4
url: https://github.com/ygorpontelo
- login: AlrasheedA
avatarUrl: https://avatars.githubusercontent.com/u/33544979?u=7fe66bf62b47682612b222e3e8f4795ef3be769b&v=4
url: https://github.com/AlrasheedA
- login: ProteinQure
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
url: https://github.com/ProteinQure
@@ -281,9 +284,6 @@ sponsors:
- login: DelfinaCare
avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4
url: https://github.com/DelfinaCare
- login: khoadaniel
avatarUrl: https://avatars.githubusercontent.com/u/84840546?v=4
url: https://github.com/khoadaniel
- login: osawa-koki
avatarUrl: https://avatars.githubusercontent.com/u/94336223?u=59c6fe6945bcbbaff87b2a794238671b060620d2&v=4
url: https://github.com/osawa-koki
@@ -327,14 +327,11 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/861044?u=5abfca5588f3e906b31583d7ee62f6de4b68aa24&v=4
url: https://github.com/browniebroke
- login: janfilips
avatarUrl: https://avatars.githubusercontent.com/u/870699?u=96df18ad355e58b9397accc55f4eeb7a86e959b0&v=4
avatarUrl: https://avatars.githubusercontent.com/u/870699?u=80702ec63f14e675cd4cdcc6ce3821d2ed207fd7&v=4
url: https://github.com/janfilips
- login: WillHogan
avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4
url: https://github.com/WillHogan
- login: NateShoffner
avatarUrl: https://avatars.githubusercontent.com/u/1712163?u=b43cc2fa3fd8bec54b7706e4b98b72543c7bfea8&v=4
url: https://github.com/NateShoffner
- login: my3
avatarUrl: https://avatars.githubusercontent.com/u/1825270?v=4
url: https://github.com/my3
@@ -344,12 +341,6 @@ sponsors:
- login: cbonoz
avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
url: https://github.com/cbonoz
- login: Patechoc
avatarUrl: https://avatars.githubusercontent.com/u/2376641?u=23b49e9eda04f078cb74fa3f93593aa6a57bb138&v=4
url: https://github.com/Patechoc
- login: larsvik
avatarUrl: https://avatars.githubusercontent.com/u/3442226?v=4
url: https://github.com/larsvik
- login: anthonycorletti
avatarUrl: https://avatars.githubusercontent.com/u/3477132?v=4
url: https://github.com/anthonycorletti
@@ -359,6 +350,9 @@ sponsors:
- login: Alisa-lisa
avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
url: https://github.com/Alisa-lisa
- login: piotrgredowski
avatarUrl: https://avatars.githubusercontent.com/u/4294480?v=4
url: https://github.com/piotrgredowski
- login: danielunderwood
avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
url: https://github.com/danielunderwood
@@ -383,6 +377,9 @@ sponsors:
- login: mattwelke
avatarUrl: https://avatars.githubusercontent.com/u/7719209?u=80f02a799323b1472b389b836d95957c93a6d856&v=4
url: https://github.com/mattwelke
- login: harsh183
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
url: https://github.com/harsh183
- login: hcristea
avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4
url: https://github.com/hcristea
@@ -428,9 +425,6 @@ sponsors:
- login: shuheng-liu
avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
url: https://github.com/shuheng-liu
- login: ghandic
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
url: https://github.com/ghandic
- login: pers0n4
avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=f211a13a7b572cbbd7779b9c8d8cb428cc7ba07e&v=4
url: https://github.com/pers0n4
@@ -458,9 +452,6 @@ sponsors:
- login: bnkc
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=527044d90b5ebb7f8dad517db5da1f45253b774b&v=4
url: https://github.com/bnkc
- login: devbruce
avatarUrl: https://avatars.githubusercontent.com/u/35563380?u=ca4e811ac7f7b3eb1600fa63285119fcdee01188&v=4
url: https://github.com/devbruce
- login: declon
avatarUrl: https://avatars.githubusercontent.com/u/36180226?v=4
url: https://github.com/declon
@@ -476,6 +467,9 @@ sponsors:
- login: ArtyomVancyan
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
url: https://github.com/ArtyomVancyan
- login: josehenriqueroveda
avatarUrl: https://avatars.githubusercontent.com/u/46685746?u=2e672057a7dbe1dba47e57c378fc0cac336022eb&v=4
url: https://github.com/josehenriqueroveda
- login: hgalytoby
avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=f4888c2c54929bd86eed0d3971d09fcb306e5088&v=4
url: https://github.com/hgalytoby
@@ -485,27 +479,36 @@ sponsors:
- login: conservative-dude
avatarUrl: https://avatars.githubusercontent.com/u/55538308?u=f250c44942ea6e73a6bd90739b381c470c192c11&v=4
url: https://github.com/conservative-dude
- login: leo-jp-edwards
avatarUrl: https://avatars.githubusercontent.com/u/58213433?u=2c128e8b0794b7a66211cd7d8ebe05db20b7e9c0&v=4
url: https://github.com/leo-jp-edwards
- login: 0417taehyun
avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4
url: https://github.com/0417taehyun
- - login: ssbarnea
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=b4bf6818deefe59952ac22fec6ed8c76de1b8f7c&v=4
url: https://github.com/ssbarnea
- login: tomast1337
avatarUrl: https://avatars.githubusercontent.com/u/15125899?u=2c2f2907012d820499e2c43632389184923513fe&v=4
url: https://github.com/tomast1337
- login: Patechoc
avatarUrl: https://avatars.githubusercontent.com/u/2376641?u=23b49e9eda04f078cb74fa3f93593aa6a57bb138&v=4
url: https://github.com/Patechoc
- login: LanceMoe
avatarUrl: https://avatars.githubusercontent.com/u/18505474?u=7fd3ead4364bdf215b6d75cb122b3811c391ef6b&v=4
url: https://github.com/LanceMoe
- login: sadikkuzu
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
url: https://github.com/sadikkuzu
- login: ruizdiazever
avatarUrl: https://avatars.githubusercontent.com/u/29817086?u=2df54af55663d246e3a4dc8273711c37f1adb117&v=4
url: https://github.com/ruizdiazever
- login: samnimoh
avatarUrl: https://avatars.githubusercontent.com/u/33413170?u=147bc516be6cb647b28d7e3b3fea3a018a331145&v=4
url: https://github.com/samnimoh
- login: danburonline
avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=2cad4388c1544e539ecb732d656e42fb07b4ff2d&v=4
url: https://github.com/danburonline
- login: iharshgor
avatarUrl: https://avatars.githubusercontent.com/u/35490011?u=2dea054476e752d9e92c9d71a9a7cc919b1c2f8e&v=4
url: https://github.com/iharshgor
- login: rwxd
avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4
url: https://github.com/rwxd
- login: ThomasPalma1
avatarUrl: https://avatars.githubusercontent.com/u/66331874?u=5763f7402d784ba189b60d704ff5849b4d0a63fb&v=4
url: https://github.com/ThomasPalma1

View File

@@ -1,16 +1,16 @@
maintainers:
- login: tiangolo
answers: 1844
prs: 430
answers: 1849
prs: 466
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4
url: https://github.com/tiangolo
experts:
- login: Kludex
count: 434
count: 463
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: dmontagu
count: 237
count: 239
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
url: https://github.com/dmontagu
- login: Mause
@@ -25,34 +25,34 @@ experts:
count: 193
avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4
url: https://github.com/JarroVGIT
- login: euri10
count: 152
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
url: https://github.com/euri10
- login: jgould22
count: 139
count: 157
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
url: https://github.com/jgould22
- login: euri10
count: 153
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
url: https://github.com/euri10
- login: phy25
count: 126
avatarUrl: https://avatars.githubusercontent.com/u/331403?u=191cd73f0c936497c8d1931a217bb3039d050265&v=4
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25
- login: iudeen
count: 118
count: 121
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
- login: raphaelauv
count: 83
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
url: https://github.com/raphaelauv
- login: ghandic
count: 71
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
url: https://github.com/ghandic
- login: ArcLightSlavik
count: 71
avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4
url: https://github.com/ArcLightSlavik
- login: ghandic
count: 71
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
url: https://github.com/ghandic
- login: falkben
count: 57
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
@@ -61,10 +61,10 @@ experts:
count: 49
avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
url: https://github.com/sm-Fifteen
- login: adriangb
- login: insomnes
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4
url: https://github.com/adriangb
avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
url: https://github.com/insomnes
- login: yinziyan1206
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
@@ -73,22 +73,22 @@ experts:
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
url: https://github.com/acidjunk
- login: insomnes
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
url: https://github.com/insomnes
- login: Dustyposa
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
url: https://github.com/Dustyposa
- login: odiseo0
count: 43
avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=2da05dab6cc8e1ade557801634760a56e4101796&v=4
url: https://github.com/odiseo0
- login: adriangb
count: 44
avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4
url: https://github.com/adriangb
- login: frankie567
count: 43
avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
url: https://github.com/frankie567
- login: odiseo0
count: 43
avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&v=4
url: https://github.com/odiseo0
- login: includeamin
count: 40
avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
@@ -97,14 +97,14 @@ experts:
count: 37
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
url: https://github.com/STeveShary
- login: chbndrhnns
count: 35
avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
url: https://github.com/chbndrhnns
- login: krishnardt
count: 35
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
url: https://github.com/krishnardt
- login: chbndrhnns
count: 35
avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
url: https://github.com/chbndrhnns
- login: panla
count: 32
avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
@@ -121,38 +121,38 @@ experts:
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
- login: acnebs
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/9054108?v=4
url: https://github.com/acnebs
- login: SirTelemak
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
url: https://github.com/SirTelemak
- login: acnebs
count: 22
avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4
url: https://github.com/acnebs
- login: rafsaf
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=f8f0d6d6e90fac39fa786228158ba7f013c74271&v=4
url: https://github.com/rafsaf
- login: chris-allnutt
- login: n8sty
count: 20
avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4
url: https://github.com/chris-allnutt
avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
url: https://github.com/n8sty
- login: nsidnev
count: 20
avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4
url: https://github.com/nsidnev
- login: n8sty
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
url: https://github.com/n8sty
- login: retnikt
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4
url: https://github.com/retnikt
- login: chris-allnutt
count: 20
avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4
url: https://github.com/chris-allnutt
- login: zoliknemet
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/22326718?u=31ba446ac290e23e56eea8e4f0c558aaf0b40779&v=4
url: https://github.com/zoliknemet
- login: retnikt
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4
url: https://github.com/retnikt
- login: Hultner
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4
@@ -198,38 +198,30 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4
url: https://github.com/jorgerpo
last_month_active:
- login: jgould22
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
url: https://github.com/jgould22
- login: Kludex
count: 13
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: abhint
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4
url: https://github.com/abhint
- login: chrisK824
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4
url: https://github.com/chrisK824
- login: jgould22
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
url: https://github.com/jgould22
- login: arjwilliams
count: 3
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/22227620?v=4
url: https://github.com/arjwilliams
- login: wu-clan
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/52145145?u=f8c9e5c8c259d248e1683fedf5027b4ee08a0967&v=4
url: https://github.com/wu-clan
- login: Ahmed-Abdou14
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=d1e1c064d57c3ad5b6481716928da840f6d5a492&v=4
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=05365b155a1ff911532e8be316acfad2e0736f98&v=4
url: https://github.com/Ahmed-Abdou14
- login: esrefzeki
- login: iudeen
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/54935247?u=193cf5a169ca05fc54995a4dceabc82c7dc6e5ea&v=4
url: https://github.com/esrefzeki
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
- login: mikeedjones
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/4087139?u=cc4a242896ac2fcf88a53acfaf190d0fe0a1f0c9&v=4
url: https://github.com/mikeedjones
top_contributors:
- login: waynerv
count: 25
@@ -240,7 +232,7 @@ top_contributors:
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
url: https://github.com/tokusumi
- login: Kludex
count: 20
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: jaystone776
@@ -264,7 +256,7 @@ top_contributors:
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
url: https://github.com/mariacamilagl
- login: Smlep
count: 10
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
url: https://github.com/Smlep
- login: Serrones
@@ -287,6 +279,10 @@ top_contributors:
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4
url: https://github.com/Alexandrhub
- login: NinaHwang
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=eee6bfe9224c71193025ab7477f4f96ceaa05c62&v=4
url: https://github.com/NinaHwang
- login: batlopes
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/33462923?u=0fb3d7acb316764616f11e4947faf080e49ad8d9&v=4
@@ -297,7 +293,7 @@ top_contributors:
url: https://github.com/wshayes
- login: samuelcolvin
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4
url: https://github.com/samuelcolvin
- login: SwftAlpc
count: 5
@@ -311,10 +307,6 @@ top_contributors:
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=f440bc9062afb3c43b9b9c6cdfdcfe31d58699ef&v=4
url: https://github.com/ComicShrimp
- login: NinaHwang
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=eee6bfe9224c71193025ab7477f4f96ceaa05c62&v=4
url: https://github.com/NinaHwang
- login: jekirl
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4
@@ -335,10 +327,18 @@ top_contributors:
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/3360631?u=5fa1f475ad784d64eb9666bdd43cc4d285dcc773&v=4
url: https://github.com/hitrust
- login: JulianMaurin
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/63545168?u=b7d15ac865268cbefc2d739e2f23d9aeeac1a622&v=4
url: https://github.com/JulianMaurin
- login: lsglucas
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
url: https://github.com/lsglucas
- login: iudeen
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
- login: axel584
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4
@@ -349,7 +349,7 @@ top_contributors:
url: https://github.com/ivan-abc
top_reviewers:
- login: Kludex
count: 122
count: 136
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: BilalAlpaslan
@@ -357,7 +357,7 @@ top_reviewers:
avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
url: https://github.com/BilalAlpaslan
- login: yezz123
count: 77
count: 78
avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=d7062cbc6eb7671d5dc9cc0e32a24ae335e0f225&v=4
url: https://github.com/yezz123
- login: tokusumi
@@ -372,20 +372,20 @@ top_reviewers:
count: 47
avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4
url: https://github.com/Laineyzhang55
- login: iudeen
count: 46
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
- login: ycd
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4
url: https://github.com/ycd
- login: iudeen
count: 44
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
- login: cikay
count: 41
avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
url: https://github.com/cikay
- login: Xewus
count: 35
count: 38
avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4
url: https://github.com/Xewus
- login: JarroVGIT
@@ -412,6 +412,10 @@ top_reviewers:
count: 26
avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
url: https://github.com/lsglucas
- login: LorhanSohaky
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4
url: https://github.com/LorhanSohaky
- login: Ryandaydev
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=809f3d1074d04bbc28012a7f17f06ea56f5bd71a&v=4
@@ -420,10 +424,6 @@ top_reviewers:
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
url: https://github.com/dmontagu
- login: LorhanSohaky
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4
url: https://github.com/LorhanSohaky
- login: rjNemo
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
@@ -434,7 +434,7 @@ top_reviewers:
url: https://github.com/hard-coders
- login: odiseo0
count: 20
avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=2da05dab6cc8e1ade557801634760a56e4101796&v=4
avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&v=4
url: https://github.com/odiseo0
- login: 0417taehyun
count: 19
@@ -456,6 +456,10 @@ top_reviewers:
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
url: https://github.com/SwftAlpc
- login: axel584
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4
url: https://github.com/axel584
- login: DevDae
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4
@@ -488,10 +492,6 @@ top_reviewers:
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4
url: https://github.com/RunningIkkyu
- login: axel584
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4
url: https://github.com/axel584
- login: ivan-abc
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4
@@ -500,6 +500,10 @@ top_reviewers:
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4
url: https://github.com/solomein-sv
- login: wdh99
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/108172295?u=8a8fb95d5afe3e0fa33257b2aecae88d436249eb&v=4
url: https://github.com/wdh99
- login: mariacamilagl
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
@@ -540,7 +544,3 @@ top_reviewers:
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4
url: https://github.com/bezaca
- login: oandersonmagalhaes
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4
url: https://github.com/oandersonmagalhaes

View File

@@ -7,7 +7,10 @@ gold:
img: https://fastapi.tiangolo.com/img/sponsors/platform-sh.png
- url: https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=main-badge
title: Fern | SDKs and API docs
img: https://fastapi.tiangolo.com/img/sponsors/fern.png
img: https://fastapi.tiangolo.com/img/sponsors/fern.svg
- url: https://www.porter.run
title: Deploy FastAPI on AWS with a few clicks
img: https://fastapi.tiangolo.com/img/sponsors/porter.png
silver:
- url: https://www.deta.sh/?ref=fastapi
title: The launchpad for all your (team's) ideas
@@ -37,3 +40,6 @@ bronze:
- url: https://www.flint.sh
title: IT expertise, consulting and development by passionate people
img: https://fastapi.tiangolo.com/img/sponsors/flint.png
- url: https://bit.ly/3JJ7y5C
title: Build cross-modal and multimodal applications on the cloud
img: https://fastapi.tiangolo.com/img/sponsors/jina2.svg

View File

@@ -17,3 +17,5 @@ logins:
- databento-bot
- nanram22
- Flint-company
- porter-dev
- fern-api

View File

@@ -12,6 +12,11 @@ A common tool is <a href="https://openapi-generator.tech/" class="external-link"
If you are building a **frontend**, a very interesting alternative is <a href="https://github.com/ferdikoomen/openapi-typescript-codegen" class="external-link" target="_blank">openapi-typescript-codegen</a>.
Another option you could consider for several languages is <a href="https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=docs-generate-clients" class="external-link" target="_blank">Fern</a>.
!!! info
<a href="https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=docs-generate-clients" class="external-link" target="_blank">Fern</a> is also a FastAPI sponsor. 😎🎉
## Generate a TypeScript Frontend Client
Let's start with a simple FastAPI application:

View File

@@ -119,6 +119,8 @@ That's why when talking about version 2.0 it's common to say "Swagger", and for
These two were chosen for being fairly popular and stable, but doing a quick search, you could find dozens of additional alternative user interfaces for OpenAPI (that you can use with **FastAPI**).
For example, you could try <a href="https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=docs-alternatives" class="external-link" target="_blank">Fern</a> which is also a FastAPI sponsor. 😎🎉
### Flask REST frameworks
There are several Flask REST frameworks, but after investing the time and work into investigating them, I found that many are discontinued or abandoned, with several standing issues that made them unfit.

View File

@@ -126,7 +126,7 @@ And if you update that local FastAPI source code when you run that Python file a
That way, you don't have to "install" your local version to be able to test every change.
!!! note "Technical Details"
This only happens when you install using this included `requiements.txt` instead of installing `pip install fastapi` directly.
This only happens when you install using this included `requirements.txt` instead of installing `pip install fastapi` directly.
That is because inside of the `requirements.txt` file, the local version of FastAPI is marked to be installed in "editable" mode, with the `-e` option.

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 349 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -2,6 +2,77 @@
## Latest Changes
## 0.101.1
### Fixes
* ✨ Add `ResponseValidationError` printable details, to show up in server error logs. PR [#10078](https://github.com/tiangolo/fastapi/pull/10078) by [@tiangolo](https://github.com/tiangolo).
### Refactors
* ✏️ Fix typo in deprecation warnings in `fastapi/params.py`. PR [#9854](https://github.com/tiangolo/fastapi/pull/9854) by [@russbiggs](https://github.com/russbiggs).
* ✏️ Fix typos in comments on internal code in `fastapi/concurrency.py` and `fastapi/routing.py`. PR [#9590](https://github.com/tiangolo/fastapi/pull/9590) by [@ElliottLarsen](https://github.com/ElliottLarsen).
### Docs
* ✏️ Fix typo in release notes. PR [#9835](https://github.com/tiangolo/fastapi/pull/9835) by [@francisbergin](https://github.com/francisbergin).
* 📝 Add external article: Build an SMS Spam Classifier Serverless Database with FaunaDB and FastAPI. PR [#9847](https://github.com/tiangolo/fastapi/pull/9847) by [@adejumoridwan](https://github.com/adejumoridwan).
* 📝 Fix typo in `docs/en/docs/contributing.md`. PR [#9878](https://github.com/tiangolo/fastapi/pull/9878) by [@VicenteMerino](https://github.com/VicenteMerino).
* 📝 Fix code highlighting in `docs/en/docs/tutorial/bigger-applications.md`. PR [#9806](https://github.com/tiangolo/fastapi/pull/9806) by [@theonlykingpin](https://github.com/theonlykingpin).
### Translations
* 🌐 Add Japanese translation for `docs/ja/docs/deployment/concepts.md`. PR [#10062](https://github.com/tiangolo/fastapi/pull/10062) by [@tamtam-fitness](https://github.com/tamtam-fitness).
* 🌐 Add Japanese translation for `docs/ja/docs/deployment/server-workers.md`. PR [#10064](https://github.com/tiangolo/fastapi/pull/10064) by [@tamtam-fitness](https://github.com/tamtam-fitness).
* 🌐 Update Japanese translation for `docs/ja/docs/deployment/docker.md`. PR [#10073](https://github.com/tiangolo/fastapi/pull/10073) by [@tamtam-fitness](https://github.com/tamtam-fitness).
* 🌐 Add Ukrainian translation for `docs/uk/docs/fastapi-people.md`. PR [#10059](https://github.com/tiangolo/fastapi/pull/10059) by [@rostik1410](https://github.com/rostik1410).
* 🌐 Add Ukrainian translation for `docs/uk/docs/tutorial/cookie-params.md`. PR [#10032](https://github.com/tiangolo/fastapi/pull/10032) by [@rostik1410](https://github.com/rostik1410).
* 🌐 Add Russian translation for `docs/ru/docs/deployment/docker.md`. PR [#9971](https://github.com/tiangolo/fastapi/pull/9971) by [@Xewus](https://github.com/Xewus).
* 🌐 Add Vietnamese translation for `docs/vi/docs/python-types.md`. PR [#10047](https://github.com/tiangolo/fastapi/pull/10047) by [@magiskboy](https://github.com/magiskboy).
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/dependencies/global-dependencies.md`. PR [#9970](https://github.com/tiangolo/fastapi/pull/9970) by [@dudyaosuplayer](https://github.com/dudyaosuplayer).
* 🌐 Add Urdu translation for `docs/ur/docs/benchmarks.md`. PR [#9974](https://github.com/tiangolo/fastapi/pull/9974) by [@AhsanSheraz](https://github.com/AhsanSheraz).
### Internal
* 🔧 Add sponsor Porter. PR [#10051](https://github.com/tiangolo/fastapi/pull/10051) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update sponsors, add Jina back as bronze sponsor. PR [#10050](https://github.com/tiangolo/fastapi/pull/10050) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump mypy from 1.4.0 to 1.4.1. PR [#9756](https://github.com/tiangolo/fastapi/pull/9756) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump mkdocs-material from 9.1.17 to 9.1.21. PR [#9960](https://github.com/tiangolo/fastapi/pull/9960) by [@dependabot[bot]](https://github.com/apps/dependabot).
## 0.101.0
### Features
* ✨ Enable Pydantic's serialization mode for responses, add support for Pydantic's `computed_field`, better OpenAPI for response models, proper required attributes, better generated clients. PR [#10011](https://github.com/tiangolo/fastapi/pull/10011) by [@tiangolo](https://github.com/tiangolo).
### Refactors
* ✅ Fix tests for compatibility with pydantic 2.1.1. PR [#9943](https://github.com/tiangolo/fastapi/pull/9943) by [@dmontagu](https://github.com/dmontagu).
* ✅ Fix test error in Windows for `jsonable_encoder`. PR [#9840](https://github.com/tiangolo/fastapi/pull/9840) by [@iudeen](https://github.com/iudeen).
### Upgrades
* 📌 Do not allow Pydantic 2.1.0 that breaks (require 2.1.1). PR [#10012](https://github.com/tiangolo/fastapi/pull/10012) by [@tiangolo](https://github.com/tiangolo).
### Translations
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/security/index.md`. PR [#9963](https://github.com/tiangolo/fastapi/pull/9963) by [@eVery1337](https://github.com/eVery1337).
* 🌐 Remove Vietnamese note about missing translation. PR [#9957](https://github.com/tiangolo/fastapi/pull/9957) by [@tiangolo](https://github.com/tiangolo).
### Internal
* 👷 Add GitHub Actions step dump context to debug external failures. PR [#10008](https://github.com/tiangolo/fastapi/pull/10008) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Restore MkDocs Material pin after the fix. PR [#10001](https://github.com/tiangolo/fastapi/pull/10001) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update the Question template to ask for the Pydantic version. PR [#10000](https://github.com/tiangolo/fastapi/pull/10000) by [@tiangolo](https://github.com/tiangolo).
* 📍 Update MkDocs Material dependencies. PR [#9986](https://github.com/tiangolo/fastapi/pull/9986) by [@tiangolo](https://github.com/tiangolo).
* 👥 Update FastAPI People. PR [#9999](https://github.com/tiangolo/fastapi/pull/9999) by [@tiangolo](https://github.com/tiangolo).
* 🐳 Update Dockerfile with compatibility versions, to upgrade later. PR [#9998](https://github.com/tiangolo/fastapi/pull/9998) by [@tiangolo](https://github.com/tiangolo).
* Add pydantic-settings to FastAPI People dependencies. PR [#9988](https://github.com/tiangolo/fastapi/pull/9988) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Update FastAPI People logic with new Pydantic. PR [#9985](https://github.com/tiangolo/fastapi/pull/9985) by [@tiangolo](https://github.com/tiangolo).
* 🍱 Update sponsors, Fern badge. PR [#9982](https://github.com/tiangolo/fastapi/pull/9982) by [@tiangolo](https://github.com/tiangolo).
* 👷 Deploy docs to Cloudflare Pages. PR [#9978](https://github.com/tiangolo/fastapi/pull/9978) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update sponsor Fern. PR [#9979](https://github.com/tiangolo/fastapi/pull/9979) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update CI debug mode with Tmate. PR [#9977](https://github.com/tiangolo/fastapi/pull/9977) by [@tiangolo](https://github.com/tiangolo).
## 0.100.1
@@ -65,7 +136,7 @@ A command line tool that will **process your code** and update most of the thing
### Pydantic v1
**This version of FastAPI still supports Pydantic v1**. And although Pydantic v1 will be deprecated at some point, ti will still be supported for a while.
**This version of FastAPI still supports Pydantic v1**. And although Pydantic v1 will be deprecated at some point, it will still be supported for a while.
This means that you can install the new Pydantic v2, and if something fails, you can install Pydantic v1 while you fix any problems you might have, but having the latest FastAPI.

View File

@@ -377,7 +377,7 @@ The `router` from `users` would overwrite the one from `items` and we wouldn't b
So, to be able to use both of them in the same file, we import the submodules directly:
```Python hl_lines="4"
```Python hl_lines="5"
{!../../../docs_src/bigger_applications/app/main.py!}
```

View File

@@ -59,6 +59,8 @@ nav:
- pt: /pt/
- ru: /ru/
- tr: /tr/
- uk: /uk/
- vi: /vi/
- zh: /zh/
- features.md
- fastapi-people.md
@@ -236,6 +238,10 @@ extra:
name: ru - русский язык
- link: /tr/
name: tr - Türkçe
- link: /uk/
name: uk
- link: /vi/
name: vi - Tiếng Việt
- link: /zh/
name: zh - 汉语
extra_css:

View File

@@ -37,7 +37,13 @@
<div class="item">
<a title="Fern | SDKs and API docs" style="display: block; position: relative;" href="https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=top-banner" target="_blank">
<span class="sponsor-badge">sponsor</span>
<img class="sponsor-image" src="/img/sponsors/fern-banner.png" />
<img class="sponsor-image" src="/img/sponsors/fern-banner.svg" />
</a>
</div>
<div class="item">
<a title="Deploy FastAPI on AWS with a few clicks" style="display: block; position: relative;" href="https://www.porter.run" target="_blank">
<span class="sponsor-badge">sponsor</span>
<img class="sponsor-image" src="/img/sponsors/porter-banner.png" />
</a>
</div>
</div>

View File

@@ -0,0 +1,323 @@
# デプロイメントのコンセプト
**FastAPI**を用いたアプリケーションをデプロイするとき、もしくはどのようなタイプのWeb APIであっても、おそらく気になるコンセプトがいくつかあります。
それらを活用することでアプリケーションを**デプロイするための最適な方法**を見つけることができます。
重要なコンセプトのいくつかを紹介します:
* セキュリティ - HTTPS
* 起動時の実行
* 再起動
* レプリケーション(実行中のプロセス数)
* メモリー
* 開始前の事前のステップ
これらが**デプロイメント**にどのような影響を与えるかを見ていきましょう。
最終的な目的は、**安全な方法で**APIクライアントに**サービスを提供**し、**中断を回避**するだけでなく、**計算リソース**(例えばリモートサーバー/仮想マシン)を可能な限り効率的に使用することです。🚀
この章では前述した**コンセプト**についてそれぞれ説明します。
この説明を通して、普段とは非常に異なる環境や存在しないであろう**将来の**環境に対し、デプロイの方法を決める上で必要な**直感**を与えてくれることを願っています。
これらのコンセプトを意識することにより、**あなた自身のAPI**をデプロイするための最適な方法を**評価**し、**設計**することができるようになるでしょう。
次の章では、FastAPIアプリケーションをデプロイするための**具体的なレシピ**を紹介します。
しかし、今はこれらの重要な**コンセプトに基づくアイデア**を確認しましょう。これらのコンセプトは、他のどのタイプのWeb APIにも当てはまります。💡
## セキュリティ - HTTPS
<!-- NOTE: https.md written in Japanese does not exist, so it redirects to English one -->
[前チャプターのHTTPSについて](./https.md){.internal-link target=_blank}では、HTTPSがどのようにAPIを暗号化するのかについて学びました。
通常、アプリケーションサーバにとって**外部の**コンポーネントである**TLS Termination Proxy**によって提供されることが一般的です。このプロキシは通信の暗号化を担当します。
さらにセキュアな通信において、HTTPS証明書の定期的な更新を行いますが、これはTLS Termination Proxyと同じコンポーネントが担当することもあれば、別のコンポーネントが担当することもあります。
### HTTPS 用ツールの例
TLS Termination Proxyとして使用できるツールには以下のようなものがあります
* Traefik
* 証明書の更新を自動的に処理 ✨
* Caddy
* 証明書の更新を自動的に処理 ✨
* Nginx
* 証明書更新のためにCertbotのような外部コンポーネントを使用
* HAProxy
* 証明書更新のためにCertbotのような外部コンポーネントを使用
* Nginx のような Ingress Controller を持つ Kubernetes
* 証明書の更新に cert-manager のような外部コンポーネントを使用
* クラウド・プロバイダーがサービスの一部として内部的に処理(下記を参照👇)
もう1つの選択肢は、HTTPSのセットアップを含んだより多くの作業を行う**クラウド・サービス**を利用することです。 このサービスには制限があったり、料金が高くなったりする可能性があります。しかしその場合、TLS Termination Proxyを自分でセットアップする必要はないです。
次の章で具体例をいくつか紹介します。
---
次に考慮すべきコンセプトは、実際のAPIを実行するプログラムUvicornに関連するものすべてです。
## プログラム と プロセス
私たちは「**プロセス**」という言葉についてたくさん話すので、その意味や「**プログラム**」という言葉との違いを明確にしておくと便利です。
### プログラムとは何か
**プログラム**という言葉は、一般的にいろいろなものを表現するのに使われます:
* プログラマが書く**コード**、**Pythonファイル**
* OSによって実行することができるファイル例: `python`, `python.exe` or `uvicorn`
* OS上で**実行**している間、CPUを使用し、メモリ上に何かを保存する特定のプログラム**プロセス**とも呼ばれる)
### プロセスとは何か
**プロセス**という言葉は通常、より具体的な意味で使われ、OSで実行されているものだけを指します先ほどの最後の説明のように
* OS上で**実行**している特定のプログラム
* これはファイルやコードを指すのではなく、OSによって**実行**され、管理されているものを指します。
* どんなプログラムやコードも、それが**実行されているときにだけ機能**します。つまり、**プロセスとして実行されているときだけ**です。
* プロセスは、ユーザーにあるいはOSによって、 **終了**(あるいは "kill")させることができます。その時点で、プロセスは実行/実行されることを停止し、それ以降は**何もできなくなります**。
* コンピュータで実行されている各アプリケーションは、実行中のプログラムや各ウィンドウなど、その背後にいくつかのプロセスを持っています。そして通常、コンピュータが起動している間、**多くのプロセスが**同時に実行されています。
* **同じプログラム**の**複数のプロセス**が同時に実行されていることがあります。
OSの「タスク・マネージャー」や「システム・モニター」または同様のツールを確認すれば、これらのプロセスの多くが実行されているの見ることができるでしょう。
例えば、同じブラウザプログラムFirefox、Chrome、Edgeなどを実行しているプロセスが複数あることがわかります。通常、1つのタブにつき1つのプロセスが実行され、さらに他のプロセスも実行されます。
<img class="shadow" src="/img/deployment/concepts/image01.png">
---
さて、**プロセス**と**プログラム**という用語の違いを確認したところで、デプロイメントについて話を続けます。
## 起動時の実行
ほとんどの場合、Web APIを作成するときは、クライアントがいつでもアクセスできるように、**常に**中断されることなく**実行される**ことを望みます。もちろん、特定の状況でのみ実行させたい特別な理由がある場合は別ですが、その時間のほとんどは、常に実行され、**利用可能**であることを望みます。
### リモートサーバー上での実行
リモートサーバークラウドサーバー、仮想マシンなどをセットアップするときにできる最も簡単なことは、ローカルで開発するときと同じように、Uvicornまたは同様のものを手動で実行することです。 この方法は**開発中**には役に立つと思われます。
しかし、サーバーへの接続が切れた場合、**実行中のプロセス**はおそらくダウンしてしまうでしょう。
そしてサーバーが再起動された場合(アップデートやクラウドプロバイダーからのマイグレーションの後など)、おそらくあなたはそれに**気づかないでしょう**。そのため、プロセスを手動で再起動しなければならないことすら気づかないでしょう。つまり、APIはダウンしたままなのです。😱
### 起動時に自動的に実行
一般的に、サーバープログラムUvicornなどはサーバー起動時に自動的に開始され、**人の介入**を必要とせずに、APIと一緒にプロセスが常に実行されるようにしたいと思われますUvicornがFastAPIアプリを実行するなど
### 別のプログラムの用意
これを実現するために、通常は**別のプログラム**を用意し、起動時にアプリケーションが実行されるようにします。そして多くの場合、他のコンポーネントやアプリケーション、例えばデータベースも実行されるようにします。
### 起動時に実行するツールの例
実行するツールの例をいくつか挙げます:
* Docker
* Kubernetes
* Docker Compose
* Swarm モードによる Docker
* Systemd
* Supervisor
* クラウドプロバイダーがサービスの一部として内部的に処理
* そのほか...
次の章で、より具体的な例を挙げていきます。
## 再起動
起動時にアプリケーションが実行されることを確認するのと同様に、失敗後にアプリケーションが**再起動**されることも確認したいと思われます。
### 我々は間違いを犯す
私たち人間は常に**間違い**を犯します。ソフトウェアには、ほとんど常に**バグ**があらゆる箇所に隠されています。🐛
### 小さなエラーは自動的に処理される
FastAPIでWeb APIを構築する際に、コードにエラーがある場合、FastAPIは通常、エラーを引き起こした単一のリクエストにエラーを含めます。🛡
クライアントはそのリクエストに対して**500 Internal Server Error**を受け取りますが、アプリケーションは完全にクラッシュするのではなく、次のリクエストのために動作を続けます。
### 重大なエラー - クラッシュ
しかしながら、**アプリケーション全体をクラッシュさせるようなコードを書いて**UvicornとPythonをクラッシュさせるようなケースもあるかもしれません。💥
それでも、ある箇所でエラーが発生したからといって、アプリケーションを停止させたままにしたくないでしょう。 少なくとも壊れていない*パスオペレーション*については、**実行し続けたい**はずです。
### クラッシュ後の再起動
しかし、実行中の**プロセス**をクラッシュさせるような本当にひどいエラーの場合、少なくとも2〜3回ほどプロセスを**再起動**させる外部コンポーネントが必要でしょう。
!!! tip
...とはいえ、アプリケーション全体が**すぐにクラッシュする**のであれば、いつまでも再起動し続けるのは意味がないでしょう。しかし、その場合はおそらく開発中か少なくともデプロイ直後に気づくと思われます。
そこで、**将来**クラッシュする可能性があり、それでも再スタートさせることに意味があるような、主なケースに焦点を当ててみます。
あなたはおそらく**外部コンポーネント**がアプリケーションの再起動を担当することを望むと考えます。 なぜなら、その時点でUvicornとPythonを使った同じアプリケーションはすでにクラッシュしており、同じアプリケーションの同じコードに対して何もできないためです。
### 自動的に再起動するツールの例
ほとんどの場合、前述した**起動時にプログラムを実行する**ために使用されるツールは、自動で**再起動**することにも利用されます。
例えば、次のようなものがあります:
* Docker
* Kubernetes
* Docker Compose
* Swarm モードによる Docker
* Systemd
* Supervisor
* クラウドプロバイダーがサービスの一部として内部的に処理
* そのほか...
## レプリケーション - プロセスとメモリー
FastAPI アプリケーションでは、Uvicorn のようなサーバープログラムを使用し、**1つのプロセス**で1度に複数のクライアントに同時に対応できます。
しかし、多くの場合、複数のワーカー・プロセスを同時に実行したいと考えるでしょう。
### 複数のプロセス - Worker
クライアントの数が単一のプロセスで処理できる数を超えており(たとえば仮想マシンがそれほど大きくない場合)、かつサーバーの CPU に**複数のコア**がある場合、同じアプリケーションで同時に**複数のプロセス**を実行させ、すべてのリクエストを分散させることができます。
同じAPIプログラムの**複数のプロセス**を実行する場合、それらは一般的に**Workerワーカー**と呼ばれます。
### ワーカー・プロセス と ポート
<!-- NOTE: https.md written in Japanese does not exist, so it redirects to English one -->
[HTTPSについて](./https.md){.internal-link target=_blank}のドキュメントで、1つのサーバーで1つのポートとIPアドレスの組み合わせでリッスンできるのは1つのプロセスだけであることを覚えていますでしょうか
これはいまだに同じです。
そのため、**複数のプロセス**を同時に持つには**ポートでリッスンしている単一のプロセス**が必要であり、それが何らかの方法で各ワーカー・プロセスに通信を送信することが求められます。
### プロセスあたりのメモリー
さて、プログラムがメモリにロードする際には、例えば機械学習モデルや大きなファイルの内容を変数に入れたりする場合では、**サーバーのメモリRAM**を少し消費します。
そして複数のプロセスは通常、**メモリを共有しません**。これは、実行中の各プロセスがそれぞれ独自の変数やメモリ等を持っていることを意味します。つまり、コード内で大量のメモリを消費している場合、**各プロセス**は同等の量のメモリを消費することになります。
### サーバーメモリー
例えば、あなたのコードが **1GBのサイズの機械学習モデル**をロードする場合、APIで1つのプロセスを実行すると、少なくとも1GBのRAMを消費します。
また、**4つのプロセス**4つのワーカーを起動すると、それぞれが1GBのRAMを消費します。つまり、合計でAPIは**4GBのRAM**を消費することになります。
リモートサーバーや仮想マシンのRAMが3GBしかない場合、4GB以上のRAMをロードしようとすると問題が発生します。🚨
### 複数プロセス - 例
この例では、2つの**ワーカー・プロセス**を起動し制御する**マネージャー・ プロセス**があります。
このマネージャー・ プロセスは、おそらくIPの**ポート**でリッスンしているものです。そして、すべての通信をワーカー・プロセスに転送します。
これらのワーカー・プロセスは、アプリケーションを実行するものであり、**リクエスト**を受けて**レスポンス**を返すための主要な計算を行い、あなたが変数に入れたものは何でもRAMにロードします。
<img src="/img/deployment/concepts/process-ram.svg">
そしてもちろん、同じマシンでは、あなたのアプリケーションとは別に、**他のプロセス**も実行されているでしょう。
興味深いことに、各プロセスが使用する**CPU**の割合は時間とともに大きく**変動**する可能性がありますが、**メモリRAM**は通常、多かれ少なかれ**安定**します。
毎回同程度の計算を行うAPIがあり、多くのクライアントがいるのであれば、**CPU使用率**もおそらく**安定**するでしょう(常に急激に上下するのではなく)。
### レプリケーション・ツールと戦略の例
これを実現するにはいくつかのアプローチがありますが、具体的な戦略については次の章(Dockerやコンテナの章など)で詳しく説明します。
考慮すべき主な制約は、**パブリックIP**の**ポート**を処理する**単一の**コンポーネントが存在しなければならないということです。
そして、レプリケートされた**プロセス/ワーカー**に通信を**送信**する方法を持つ必要があります。
考えられる組み合わせと戦略をいくつか紹介します:
* **Gunicorn**が**Uvicornワーカー**を管理
* Gunicornは**IP**と**ポート**をリッスンする**プロセスマネージャ**で、レプリケーションは**複数のUvicornワーカー・プロセス**を持つことによって行われる。
* **Uvicorn**が**Uvicornワーカー**を管理
* 1つのUvicornの**プロセスマネージャー**が**IP**と**ポート**をリッスンし、**複数のUvicornワーカー・プロセス**を起動する。
* **Kubernetes**やその他の分散**コンテナ・システム**
* **Kubernetes**レイヤーの何かが**IP**と**ポート**をリッスンする。レプリケーションは、**複数のコンテナ**にそれぞれ**1つのUvicornプロセス**を実行させることで行われる。
* **クラウド・サービス**によるレプリケーション
* クラウド・サービスはおそらく**あなたのためにレプリケーションを処理**します。**実行するプロセス**や使用する**コンテナイメージ**を定義できるかもしれませんが、いずれにせよ、それはおそらく**単一のUvicornプロセス**であり、クラウドサービスはそのレプリケーションを担当するでしょう。
!!! tip
これらの**コンテナ**やDockerそしてKubernetesに関する項目が、まだあまり意味をなしていなくても心配しないでください。
<!-- NOTE: the current version of docker.md is outdated compared to English one. -->
コンテナ・イメージ、Docker、Kubernetesなどについては、次の章で詳しく説明します: [コンテナ内のFastAPI - Docker](./docker.md){.internal-link target=_blank}.
## 開始前の事前のステップ
アプリケーションを**開始する前**に、いくつかのステップを実行したい場合が多くあります。
例えば、**データベース・マイグレーション** を実行したいかもしれません。
しかしほとんどの場合、これらの手順を**1度**に実行したいと考えるでしょう。
そのため、アプリケーションを開始する前の**事前のステップ**を実行する**単一のプロセス**を用意したいと思われます。
そして、それらの事前のステップを実行しているのが単一のプロセスであることを確認する必要があります。このことはその後アプリケーション自体のために**複数のプロセス**(複数のワーカー)を起動した場合も同様です。
これらのステップが**複数のプロセス**によって実行された場合、**並列**に実行されることによって作業が**重複**することになります。そして、もしそのステップがデータベースのマイグレーションのような繊細なものであった場合、互いに競合を引き起こす可能性があります。
もちろん、事前のステップを何度も実行しても問題がない場合もあり、その際は対処がかなり楽になります。
!!! tip
また、セットアップによっては、アプリケーションを開始する前の**事前のステップ**が必要ない場合もあることを覚えておいてください。
その場合は、このようなことを心配する必要はないです。🤷
### 事前ステップの戦略例
これは**システムを**デプロイする方法に**大きく依存**するだろうし、おそらくプログラムの起動方法や再起動の処理などにも関係してくるでしょう。
考えられるアイデアをいくつか挙げてみます:
* アプリコンテナの前に実行されるKubernetesのInitコンテナ
* 事前のステップを実行し、アプリケーションを起動するbashスクリプト
* 利用するbashスクリプトを起動再起動したり、エラーを検出したりする方法は以前として必要になるでしょう。
!!! tip
<!-- NOTE: the current version of docker.md is outdated compared to English one. -->
コンテナを使った具体的な例については、次の章で紹介します: [コンテナ内のFastAPI - Docker](./docker.md){.internal-link target=_blank}.
## リソースの利用
あなたのサーバーは**リソース**であり、プログラムを実行しCPUの計算時間や利用可能なRAMメモリを消費または**利用**することができます。
システムリソースをどれくらい消費/利用したいですか? 「少ない方が良い」と考えるのは簡単かもしれないですが、実際には、**クラッシュせずに可能な限り**最大限に活用したいでしょう。
3台のサーバーにお金を払っているにも関わらず、そのRAMとCPUを少ししか使っていないとしたら、おそらく**お金を無駄にしている** 💸、おそらく**サーバーの電力を無駄にしている** 🌎ことになるでしょう。
その場合は、サーバーを2台だけにして、そのリソースCPU、メモリ、ディスク、ネットワーク帯域幅などをより高い割合で使用する方がよいでしょう。
一方、2台のサーバーがあり、そのCPUとRAMの**100%を使用している**場合、ある時点で1つのプロセスがより多くのメモリを要求し、サーバーはディスクを「メモリ」として使用しないといけません。何千倍も遅くなる可能性があります。
もしくは**クラッシュ**することもあれば、あるいはあるプロセスが何らかの計算をする必要があり、そしてCPUが再び空くまで待たなければならないかもしれません。
この場合、**1つ余分なサーバー**を用意し、その上でいくつかのプロセスを実行し、すべてのサーバーが**十分なRAMとCPU時間を持つようにする**のがよいでしょう。
また、何らかの理由でAPIの利用が急増する可能性もあります。もしかしたらそれが流行ったのかもしれないし、他のサービスやボットが使い始めたのかもしれないです。そのような場合に備えて、余分なリソースを用意しておくと安心でしょう。
例えば、リソース使用率の**50%から90%の範囲**で**任意の数字**をターゲットとすることができます。
重要なのは、デプロイメントを微調整するためにターゲットを設定し測定することが、おそらく使用したい主要な要素であることです。
`htop`のような単純なツールを使って、サーバーで使用されているCPUやRAM、あるいは各プロセスで使用されている量を見ることができます。あるいは、より複雑な監視ツールを使って、サーバに分散して使用することもできます。
## まとめ
アプリケーションのデプロイ方法を決定する際に、考慮すべきであろう主要なコンセプトのいくつかを紹介していきました:
* セキュリティ - HTTPS
* 起動時の実行
* 再起動
* レプリケーション(実行中のプロセス数)
* メモリー
* 開始前の事前ステップ
これらの考え方とその適用方法を理解することで、デプロイメントを設定したり調整したりする際に必要な直感的な判断ができるようになるはずです。🤓
次のセクションでは、あなたが取り得る戦略について、より具体的な例を挙げます。🚀

View File

@@ -1,71 +1,157 @@
# Dockerを使用したデプロイ
# コンテナ内のFastAPI - Docker
このセクションでは以下の使い方の紹介とガイドへのリンクが確認できます:
FastAPIアプリケーションをデプロイする場合、一般的なアプローチは**Linuxコンテナ・イメージ**をビルドすることです。
* **5分**程度で、**FastAPI** のアプリケーションを、パフォーマンスを最大限に発揮するDockerイメージ (コンテナ)にする
* (オプション) 開発者として必要な範囲でHTTPSを理解する。
* **20分**程度で、自動的なHTTPS生成とともにDockerのSwarmモード クラスタをセットアップする (月5ドルのシンプルなサーバー上で)。
* **10分**程度で、DockerのSwarmモード クラスタを使って、HTTPSなどを使用した完全な**FastAPI** アプリケーションの作成とデプロイ。
基本的には <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>を用いて行われます。生成されたコンテナ・イメージは、いくつかの方法のいずれかでデプロイできます
デプロイのために、<a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a> を利用できます。セキュリティ、再現性、開発のシンプルさなどに利点があります。
Linuxコンテナの使用には、**セキュリティ**、**反復可能性(レプリカビリティ)**、**シンプリシティ**など、いくつかの利点があります。
Dockerを使う場合、公式のDockerイメージが利用できます:
!!! tip
TODO: なぜか遷移できない
お急ぎで、すでにこれらの情報をご存じですか? [以下の`Dockerfile`の箇所👇](#build-a-docker-image-for-fastapi)へジャンプしてください。
## <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>
このイメージは「自動チューニング」機構を含んでいます。犠牲を払うことなく、ただコードを加えるだけで自動的に高パフォーマンスを実現できます。
ただし、環境変数や設定ファイルを使って全ての設定の変更や更新を行えます。
!!! tip "豆知識"
全ての設定とオプションを確認するには、Dockerイメージページを開いて下さい: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
## `Dockerfile` の作成
* プロジェクトディレクトリへ移動。
* 以下の`Dockerfile` を作成:
<details>
<summary>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
[Bigger Applications with Multiple Files](tutorial/bigger-applications.md){.internal-link target=_blank} セクションに倣う場合は、`Dockerfile` は上記の代わりに、以下の様になるかもしれません:
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
COPY ./app /app/app
```
### Raspberry Piなどのアーキテクチャ
Raspberry Pi (ARMプロセッサ搭載)やそれ以外のアーキテクチャでDockerが作動している場合、(マルチアーキテクチャである) Pythonベースイメージを使って、一から`Dockerfile`を作成し、Uvicornを単体で使用できます。
この場合、`Dockerfile` は以下の様になるかもしれません:
```Dockerfile
FROM python:3.7
RUN pip install fastapi uvicorn
EXPOSE 80
COPY ./app /app
COPY ./app /code/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
# If running behind a proxy like Nginx or Traefik add --proxy-headers
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
```
## **FastAPI** コードの作成
</details>
* `app` ディレクトリを作成し、移動。
* 以下の`main.py` ファイルを作成:
## コンテナとは何か
コンテナ主にLinuxコンテナは、同じシステム内の他のコンテナ他のアプリケーションやコンポーネントから隔離された状態を保ちながら、すべての依存関係や必要なファイルを含むアプリケーションをパッケージ化する非常に**軽量**な方法です。
Linuxコンテナは、ホストマシン、仮想マシン、クラウドサーバーなどの同じLinuxカーネルを使用して実行されます。これは、OS全体をエミュレートする完全な仮想マシンと比べて非常に軽量であることを意味します。
このように、コンテナは**リソースをほとんど消費しません**が、プロセスを直接実行するのに匹敵する量です(仮想マシンはもっと消費します)。
コンテナはまた、独自の**分離された**実行プロセス通常は1つのプロセスのみや、ファイルシステム、ネットワークを持ちます。 このことはデプロイ、セキュリティ、開発などを簡素化させます。
## コンテナ・イメージとは何か
**コンテナ**は、**コンテナ・イメージ**から実行されます。
コンテナ・イメージは、コンテナ内に存在すべきすべてのファイルや環境変数、そしてデフォルトのコマンド/プログラムを**静的に**バージョン化したものです。 ここでの**静的**とは、コンテナ**イメージ**は実行されておらず、パッケージ化されたファイルとメタデータのみであることを意味します。
保存された静的コンテンツである「**コンテナイメージ**」とは対照的に、「**コンテナ**」は通常、実行中のインスタンス、つまり**実行**されているものを指します。
**コンテナ**が起動され実行されるとき(**コンテナイメージ**から起動されるとき)、ファイルや環境変数などが作成されたり変更されたりする可能性があります。
これらの変更はそのコンテナ内にのみ存在しますが、基盤となるコンテナ・イメージには残りません(ディスクに保存されません)。
コンテナイメージは **プログラム** ファイルやその内容、例えば `python``main.py` ファイルに匹敵します。
そして、**コンテナ**自体は(**コンテナイメージ**とは対照的に)イメージをもとにした実際の実行中のインスタンスであり、**プロセス**に匹敵します。
実際、コンテナが実行されているのは、**プロセスが実行されている**ときだけです(通常は単一のプロセスだけです)。 コンテナ内で実行中のプロセスがない場合、コンテナは停止します。
## コンテナ・イメージ
Dockerは、**コンテナ・イメージ**と**コンテナ**を作成・管理するための主要なツールの1つです。
そして、DockerにはDockerイメージコンテナを共有する<a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a>というものがあります。
Docker Hubは 多くのツールや環境、データベース、アプリケーションに対応している予め作成された**公式のコンテナ・イメージ**をパブリックに提供しています。
例えば、公式イメージの1つに<a href="https://hub.docker.com/_/python" class="external-link" target="_blank">Python Image</a>があります。
その他にも、データベースなどさまざまなイメージがあります:
* <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.
予め作成されたコンテナ・イメージを使用することで、異なるツールを**組み合わせて**使用することが非常に簡単になります。例えば、新しいデータベースを試す場合に特に便利です。ほとんどの場合、**公式イメージ**を使い、環境変数で設定するだけで良いです。
そうすれば多くの場合、コンテナとDockerについて学び、その知識をさまざまなツールやコンポーネントによって再利用することができます。
つまり、データベース、Pythonアプリケーション、Reactフロントエンド・アプリケーションを備えたウェブ・サーバーなど、さまざまなものを**複数のコンテナ**で実行し、それらを内部ネットワーク経由で接続します。
すべてのコンテナ管理システムDockerやKubernetesなどには、こうしたネットワーキング機能が統合されています。
## コンテナとプロセス
通常、**コンテナ・イメージ**はそのメタデータに**コンテナ**の起動時に実行されるデフォルトのプログラムまたはコマンドと、そのプログラムに渡されるパラメータを含みます。コマンドラインでの操作とよく似ています。
**コンテナ**が起動されると、そのコマンド/プログラムが実行されます(ただし、別のコマンド/プログラムをオーバーライドして実行させることもできます)。
コンテナは、**メイン・プロセス**(コマンドまたはプログラム)が実行されている限り実行されます。
コンテナは通常**1つのプロセス**を持ちますが、メイン・プロセスからサブ・プロセスを起動することも可能で、そうすれば同じコンテナ内に**複数のプロセス**を持つことになります。
しかし、**少なくとも1つの実行中のプロセス**がなければ、実行中のコンテナを持つことはできないです。メイン・プロセスが停止すれば、コンテナも停止します。
## Build a Docker Image for FastAPI
ということで、何か作りましょう!🚀
FastAPI用の**Dockerイメージ**を、**公式Python**イメージに基づいて**ゼロから**ビルドする方法をお見せします。
これは**ほとんどの場合**にやりたいことです。例えば:
* **Kubernetes**または同様のツールを使用する場合
* **Raspberry Pi**で実行する場合
* コンテナ・イメージを実行してくれるクラウド・サービスなどを利用する場合
### パッケージ要件package requirements
アプリケーションの**パッケージ要件**は通常、何らかのファイルに記述されているはずです。
パッケージ要件は主に**インストール**するために使用するツールに依存するでしょう。
最も一般的な方法は、`requirements.txt` ファイルにパッケージ名とそのバージョンを 1 行ずつ書くことです。
もちろん、[FastAPI バージョンについて](./versions.md){.internal-link target=_blank}で読んだのと同じアイデアを使用して、バージョンの範囲を設定します。
例えば、`requirements.txt` は次のようになります:
```
fastapi>=0.68.0,<0.69.0
pydantic>=1.8.0,<2.0.0
uvicorn>=0.15.0,<0.16.0
```
そして通常、例えば `pip` を使ってこれらのパッケージの依存関係をインストールします:
<div class="termy">
```console
$ pip install -r requirements.txt
---> 100%
Successfully installed fastapi pydantic uvicorn
```
</div>
!!! info
パッケージの依存関係を定義しインストールするためのフォーマットやツールは他にもあります。
Poetryを使った例は、後述するセクションでご紹介します。👇
### **FastAPI**コードを作成する
* `app` ディレクトリを作成し、その中に入ります
* 空のファイル `__init__.py` を作成します
* `main.py` ファイルを作成します:
```Python
from typing import Optional
from typing import Union
from fastapi import FastAPI
@@ -78,23 +164,136 @@ 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}
```
* ここでは、以下の様なディレクトリ構造になっているはずです:
### Dockerfile
同じプロジェクト・ディレクトリに`Dockerfile`というファイルを作成します:
```{ .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 ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. 公式のPythonベースイメージから始めます
2. 現在の作業ディレクトリを `/code` に設定します
ここに `requirements.txt` ファイルと `app` ディレクトリを置きます。
3. 要件が書かれたファイルを `/code` ディレクトリにコピーします
残りのコードではなく、最初に必要なファイルだけをコピーしてください。
このファイルは**頻繁には変更されない**ので、Dockerはこのステップではそれを検知し**キャッシュ**を使用し、次のステップでもキャッシュを有効にします。
4. 要件ファイルにあるパッケージの依存関係をインストールします
`--no-cache-dir` オプションはダウンロードしたパッケージをローカルに保存しないように `pip` に指示します。これは、同じパッケージをインストールするために `pip` を再度実行する場合にのみ有効ですが、コンテナで作業する場合はそうではないです。
!!! note
`--no-cache-dir`は`pip`に関連しているだけで、Dockerやコンテナとは何の関係もないです。
`--upgrade` オプションは、パッケージが既にインストールされている場合、`pip` にアップグレードするように指示します。
何故ならファイルをコピーする前のステップは**Dockerキャッシュ**によって検出される可能性があるためであり、このステップも利用可能な場合は**Dockerキャッシュ**を使用します。
このステップでキャッシュを使用すると、開発中にイメージを何度もビルドする際に、**毎回**すべての依存関係を**ダウンロードしてインストールする**代わりに多くの**時間**を**節約**できます。
5. ./app` ディレクトリを `/code` ディレクトリの中にコピーする。
これには**最も頻繁に変更される**すべてのコードが含まれているため、Dockerの**キャッシュ**は**これ以降のステップ**に簡単に使用されることはありません。
そのため、コンテナイメージのビルド時間を最適化するために、`Dockerfile`**最後** にこれを置くことが重要です。
6. `uvicorn`サーバーを実行するための**コマンド**を設定します
`CMD` は文字列のリストを取り、それぞれの文字列はスペースで区切られたコマンドラインに入力するものです。
このコマンドは **現在の作業ディレクトリ**から実行され、上記の `WORKDIR /code` にて設定した `/code` ディレクトリと同じです。
そのためプログラムは `/code` で開始しその中にあなたのコードがある `./app` ディレクトリがあるので、**Uvicorn** は `app.main` から `app` を参照し、**インポート** することができます。
!!! tip
コード内の"+"の吹き出しをクリックして、各行が何をするのかをレビューしてください。👆
これで、次のようなディレクトリ構造になるはずです:
```
.
├── app
│   ├── __init__.py
│ └── main.py
── Dockerfile
── Dockerfile
└── requirements.txt
```
## Dockerイメージをビルド
#### TLS Termination Proxyの裏側
* プロジェクトディレクトリ (`app` ディレクトリを含んだ、`Dockerfile` のある場所) へ移動
* FastAPIイメージのビルド:
Nginx や Traefik のような TLS Termination Proxy (ロードバランサ) の後ろでコンテナを動かしている場合は、`--proxy-headers`オプションを追加します。
このオプションは、Uvicornにプロキシ経由でHTTPSで動作しているアプリケーションに対して、送信されるヘッダを信頼するよう指示します。
```Dockerfile
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
```
#### Dockerキャッシュ
この`Dockerfile`には重要なトリックがあり、まず**依存関係だけのファイル**をコピーします。その理由を説明します。
```Dockerfile
COPY ./requirements.txt /code/requirements.txt
```
Dockerや他のツールは、これらのコンテナイメージを**段階的に**ビルドし、**1つのレイヤーを他のレイヤーの上に**追加します。`Dockerfile`の先頭から開始し、`Dockerfile`の各命令によって作成されたファイルを追加していきます。
Dockerや同様のツールは、イメージをビルドする際に**内部キャッシュ**も使用します。前回コンテナイメージを構築したときからファイルが変更されていない場合、ファイルを再度コピーしてゼロから新しいレイヤーを作成する代わりに、**前回作成した同じレイヤーを再利用**します。
ただファイルのコピーを避けるだけではあまり改善されませんが、そのステップでキャッシュを利用したため、**次のステップ**でキャッシュを使うことができます。
例えば、依存関係をインストールする命令のためにキャッシュを使うことができます:
```Dockerfile
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
```
パッケージ要件のファイルは**頻繁に変更されることはありません**。そのため、そのファイルだけをコピーすることで、Dockerはそのステップでは**キャッシュ**を使用することができます。
そして、Dockerは**次のステップのためにキャッシュ**を使用し、それらの依存関係をダウンロードしてインストールすることができます。そして、ここで**多くの時間を節約**します。✨ ...そして退屈な待ち時間を避けることができます。😪😆
パッケージの依存関係をダウンロードしてインストールするには**数分**かかりますが、**キャッシュ**を使えば**せいぜい数秒**です。
加えて、開発中にコンテナ・イメージを何度もビルドして、コードの変更が機能しているかどうかをチェックすることになるため、多くの時間を節約することができます。
そして`Dockerfile`の最終行の近くですべてのコードをコピーします。この理由は、**最も頻繁に**変更されるものなので、このステップの後にあるものはほとんどキャッシュを使用することができないのためです。
```Dockerfile
COPY ./app /code/app
```
### Dockerイメージをビルドする
すべてのファイルが揃ったので、コンテナ・イメージをビルドしましょう。
* プロジェクトディレクトリに移動します(`Dockerfile`がある場所で、`app`ディレクトリがあります)
* FastAPI イメージをビルドします:
<div class="termy">
@@ -106,9 +305,14 @@ $ docker build -t myimage .
</div>
## Dockerコンテナを起動
!!! tip
末尾の `.` に注目してほしいです。これは `./` と同じ意味です。 これはDockerにコンテナイメージのビルドに使用するディレクトリを指示します。
* 用意したイメージを基にしたコンテナの起動:
この場合、同じカレント・ディレクトリ(`.`)です。
### Dockerコンテナの起動する
* イメージに基づいてコンテナを実行します:
<div class="termy">
@@ -118,62 +322,394 @@ $ docker run -d --name mycontainer -p 80:80 myimage
</div>
これで、Dockerコンテナ内に最適化されたFastAPIサーバが動作しています。使用しているサーバ (そしてCPUコア数) に沿った自動チューニングが行われています。
## 確認する
## 確認
Dockerコンテナの<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> や <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> (またはそれに相当するDockerホストを使用したものといったURLで確認できるはずです。
DockerコンテナのURLで確認できるはずです。例えば: <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> や <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> (もしくはDockerホストを使用したこれらと同等のもの)。
以下の様なものが返されます:
アクセスすると以下のようなものが表示されます:
```JSON
{"item_id": 5, "q": "somequery"}
```
## 対話的APIドキュメント
## インタラクティブなAPIドキュメント
こで、<a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> や <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (もしくはDockerホストを使用したこれらと同等のもの) を開いて下さい。
れらのURLにもアクセスできます: <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> や <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (またはそれに相当するDockerホストを使用したもの)
自動生成された対話APIドキュメントが確認できます (<a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>によって提供されます):
アクセスすると、自動対話APIドキュメント<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)
## その他のAPIドキュメント
## 代替のAPIドキュメント
また同様に、<a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> や <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (もしくはDockerホストを使用したこれらと同等のもの) を開いて下さい
また、<a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> や <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (またはそれに相当するDockerホストを使用したもの)にもアクセスできます
の自動生成された対話的なAPIドキュメントが確認できます (<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>によって提供されます):
代替の自動ドキュメント(<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
## 単一ファイルのFastAPIでDockerイメージをビルドする
<a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>は、高性能なリバースプロキシ/ロードバランサーです。「TLSターミネーションプロキシ」ジョブを実行できます他の機能と切り離して
FastAPI が単一のファイル、例えば `./app` ディレクトリのない `main.py` の場合、ファイル構造は次のようになります:
```
.
├── Dockerfile
├── main.py
└── requirements.txt
```
Let's Encryptと統合されています。そのため、証明書の取得と更新を含むHTTPSに関するすべての処理を実行できます。
そうすれば、`Dockerfile`の中にファイルをコピーするために、対応するパスを変更するだけでよいです:
また、Dockerとも統合されています。したがって、各アプリケーション構成でドメインを宣言し、それらの構成を読み取って、HTTPS証明書を生成し、構成に変更を加えることなく、アプリケーションにHTTPSを自動的に提供できます。
```{ .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 ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. main.py`ファイルを `/code` ディレクトリに直接コピーします。
2. Uvicornを実行し、`main`から`app`オブジェクトをインポートするように指示します(`app.main`からインポートするのではなく)。
次にUvicornコマンドを調整して、`app.main` の代わりに新しいモジュール `main` を使用し、FastAPIオブジェクトである `app` をインポートします。
## デプロイメントのコンセプト
コンテナという観点から、[デプロイのコンセプト](./concepts.md){.internal-link target=_blank}に共通するいくつかについて、もう一度説明しましょう。
コンテナは主に、アプリケーションの**ビルドとデプロイ**のプロセスを簡素化するためのツールですが、これらの**デプロイのコンセプト**を扱うための特定のアプローチを強制するものではないです。
**良いニュース**は、それぞれの異なる戦略には、すべてのデプロイメントのコンセプトをカバーする方法があるということです。🎉
これらの**デプロイメントのコンセプト**をコンテナの観点から見直してみましょう:
* セキュリティ - HTTPS
* 起動時の実行
* 再起動
* **レプリケーション(実行中のプロセス数)**
* メモリ
* 開始前の事前ステップ
## HTTPS
FastAPI アプリケーションの **コンテナ・イメージ**(および後で実行中の **コンテナ**だけに焦点を当てると、通常、HTTPSは別のツールを用いて**外部で**処理されます。
例えば<a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>のように、**HTTPS**と**証明書**の**自動**取得を扱う別のコンテナである可能性もあります。
!!! tip
TraefikはDockerやKubernetesなどと統合されているので、コンテナ用のHTTPSの設定や構成はとても簡単です。
あるいは、コンテナ内でアプリケーションを実行しながらクラウド・プロバイダーがサービスの1つとしてHTTPSを処理することもできます。
## 起動時および再起動時の実行
通常、コンテナの**起動と実行**を担当する別のツールがあります。
それは直接**Docker**であったり、**Docker Compose**であったり、**Kubernetes**であったり、**クラウドサービス**であったりします。
ほとんどの場合またはすべての場合、起動時にコンテナを実行し、失敗時に再起動を有効にする簡単なオプションがあります。例えばDockerでは、コマンドラインオプションの`--restart`が該当します。
コンテナを使わなければ、アプリケーションを起動時や再起動時に実行させるのは面倒で難しいかもしれません。しかし、**コンテナ**で作業する場合、ほとんどのケースでその機能はデフォルトで含まれています。✨
## レプリケーション - プロセス数
**Kubernetes** や Docker Swarm モード、Nomad、あるいは複数のマシン上で分散コンテナを管理するための同様の複雑なシステムを使ってマシンの<abbr title="何らかの方法で接続され、一緒に動作するように構成されたマシンのグループ">クラスター</abbr>を構成している場合、 各コンテナでWorkerを持つGunicornのような**プロセスマネージャ**を使用する代わりに、**クラスター・レベル**で**レプリケーション**を処理したいと思うでしょう。
Kubernetesのような分散コンテナ管理システムの1つは通常、入ってくるリクエストの**ロードバランシング**をサポートしながら、**コンテナのレプリケーション**を処理する統合された方法を持っています。このことはすべて**クラスタレベル**にてです。
そのような場合、UvicornワーカーでGunicornのようなものを実行するのではなく、[上記の説明](#dockerfile)のように**Dockerイメージをゼロから**ビルドし、依存関係をインストールして、**単一のUvicornプロセス**を実行したいでしょう。
### ロードバランサー
コンテナを使用する場合、通常はメイン・ポート**でリスニング**しているコンポーネントがあるはずです。それはおそらく、**HTTPS**を処理するための**TLS Termination Proxy**でもある別のコンテナであったり、同様のツールであったりするでしょう。
このコンポーネントはリクエストの **負荷** を受け、 (うまくいけば) その負荷を**バランスよく** ワーカーに分配するので、一般に **ロードバランサ** とも呼ばれます。
!!! tip
  HTTPSに使われるものと同じ**TLS Termination Proxy**コンポーネントは、おそらく**ロードバランサー**にもなるでしょう。
そしてコンテナで作業する場合、コンテナの起動と管理に使用する同じシステムには、**ロードバランサー****TLS Termination Proxy**の可能性もある)から**ネットワーク通信**HTTPリクエストなどをアプリのあるコンテナ複数可に送信するための内部ツールが既にあるはずです。
### 1つのロードバランサー - 複数のワーカーコンテナー
**Kubernetes**や同様の分散コンテナ管理システムで作業する場合、その内部のネットワーキングのメカニズムを使用することで、メインの**ポート**でリッスンしている単一の**ロードバランサー**が、アプリを実行している可能性のある**複数のコンテナ**に通信(リクエスト)を送信できるようになります。
アプリを実行するこれらのコンテナには、通常**1つのプロセス**たとえば、FastAPIアプリケーションを実行するUvicornプロセスがあります。これらはすべて**同一のコンテナ**であり同じものを実行しますが、それぞれが独自のプロセスやメモリなどを持ちます。そうすることで、CPUの**異なるコア**、あるいは**異なるマシン**での**並列化**を利用できます。
そして、**ロードバランサー**を備えた分散コンテナシステムは、**順番に**あなたのアプリを含む各コンテナに**リクエストを分配**します。つまり、各リクエストは、あなたのアプリを実行している複数の**レプリケートされたコンテナ**の1つによって処理されます。
そして通常、この**ロードバランサー**は、クラスタ内の*他の*アプリケーション例えば、異なるドメインや異なるURLパスのプレフィックスの配下へのリクエストを処理することができ、その通信をクラスタ内で実行されている*他の*アプリケーションのための適切なコンテナに送信します。
### 1コンテナにつき1プロセス
この種のシナリオでは、すでにクラスタ・レベルでレプリケーションを処理しているため、おそらくコンテナごとに**単一のUvicornプロセス**を持ちたいでしょう。
この場合、Uvicornワーカーを持つGunicornのようなプロセスマネージャーや、Uvicornワーカーを使うUvicornは**避けたい**でしょう。**コンテナごとにUvicornのプロセスは1つだけ**にしたいでしょう(おそらく複数のコンテナが必要でしょう)。
GunicornやUvicornがUvicornワーカーを管理するようにコンテナ内に別のプロセスマネージャーを持つことは、クラスターシステムですでに対処しているであろう**不要な複雑さ**を追加するだけです。
### Containers with Multiple Processes and Special Cases
もちろん、**特殊なケース**として、**Gunicornプロセスマネージャ**を持つ**コンテナ**内で複数の**Uvicornワーカープロセス**を起動させたい場合があります。
このような場合、**公式のDockerイメージ**を使用することができます。このイメージには、複数の**Uvicornワーカープロセス**を実行するプロセスマネージャとして**Gunicorn**が含まれており、現在のCPUコアに基づいてワーカーの数を自動的に調整するためのデフォルト設定がいくつか含まれています。詳しくは後述の[Gunicornによる公式Dockerイメージ - Uvicorn](#gunicornによる公式dockerイメージ---Uvicorn)で説明します。
以下は、それが理にかなっている場合の例です:
#### シンプルなアプリケーション
アプリケーションを**シンプル**な形で実行する場合、プロセス数の細かい調整が必要ない場合、自動化されたデフォルトを使用するだけで、コンテナ内にプロセスマネージャが必要かもしれません。例えば、公式Dockerイメージでシンプルな設定が可能です。
#### Docker Compose
Docker Composeで**シングルサーバ**(クラスタではない)にデプロイすることもできますので、共有ネットワークと**ロードバランシング**を維持しながらDocker Composeでコンテナのレプリケーションを管理する簡単な方法はないでしょう。
その場合、**単一のコンテナ**で、**プロセスマネージャ**が内部で**複数のワーカープロセス**を起動するようにします。
#### Prometheusとその他の理由
また、**1つのコンテナ**に**1つのプロセス**を持たせるのではなく、**1つのコンテナ**に**複数のプロセス**を持たせる方が簡単だという**他の理由**もあるでしょう。
例えば、(セットアップにもよりますが)Prometheusエクスポーターのようなツールを同じコンテナ内に持つことができます。
この場合、**複数のコンテナ**があると、デフォルトでは、Prometheusが**メトリクスを**読みに来たとき、すべてのレプリケートされたコンテナの**蓄積されたメトリクス**を取得するのではなく、毎回**単一のコンテナ**(その特定のリクエストを処理したコンテナ)のものを取得することになります。
その場合、**複数のプロセス**を持つ**1つのコンテナ**を用意し、同じコンテナ上のローカルツール例えばPrometheusエクスポーターがすべての内部プロセスのPrometheusメトリクスを収集し、その1つのコンテナ上でそれらのメトリクスを公開する方がシンプルかもしれません。
---
次のセクションに進み、この情報とツールを使用して、すべてを組み合わせます。
重要なのは、盲目的に従わなければならない普遍のルールはないということです。
## TraefikとHTTPSを使用したDocker Swarmモードのクラスタ
これらのアイデアは、**あなた自身のユースケース**を評価し、あなたのシステムに最適なアプローチを決定するために使用することができます:
HTTPSを処理する証明書の取得と更新を含むTraefikを使用して、Docker Swarmモードのクラスタを数分20分程度でセットアップできます。
* セキュリティ - HTTPS
* 起動時の実行
* 再起動
* **レプリケーション(実行中のプロセス数)**
* メモリ
* 開始前の事前ステップ
Docker Swarmモードを使用することで、1台のマシンの「クラスタ」から開始でき1か月あたり5ドルのサーバーでもできます、後から必要なだけサーバーを拡張できます。
## メモリー
TraefikおよびHTTPS処理を備えたDocker Swarm Modeクラスターをセットアップするには、次のガイドに従います:
コンテナごとに**単一のプロセスを実行する**と、それらのコンテナレプリケートされている場合は1つ以上によって消費される多かれ少なかれ明確に定義された、安定し制限された量のメモリを持つことになります
### <a href="https://medium.com/@tiangolo/docker-swarm-mode-and-traefik-for-a-https-cluster-20328dba6232" class="external-link" target="_blank">Docker Swarm Mode and Traefik for an HTTPS cluster</a>
そして、コンテナ管理システム(**Kubernetes**など)の設定で、同じメモリ制限と要件を設定することができます。
### FastAPIアプリケーションのデプロイ
そうすれば、コンテナが必要とするメモリ量とクラスタ内のマシンで利用可能なメモリ量を考慮して、**利用可能なマシン**に**コンテナ**をレプリケートできるようになります。
すべてを設定するための最も簡単な方法は、[**FastAPI** Project Generators](../project-generation.md){.internal-link target=_blank}を使用することでしょう
アプリケーションが**シンプル**なものであれば、これはおそらく**問題にはならない**でしょうし、ハードなメモリ制限を指定する必要はないかもしれないです
上述したTraefikとHTTPSを備えたDocker Swarm クラスタが統合されるように設計されています。
しかし、**多くのメモリを使用**している場合(たとえば**機械学習**モデルなど)、どれだけのメモリを消費しているかを確認し、**各マシンで実行するコンテナの数**を調整する必要があります(そしておそらくクラスタにマシンを追加します
2分程度でプロジェクトが生成されます。
**コンテナごとに複数のプロセス**を実行する場合たとえば公式のDockerイメージで、起動するプロセスの数が**利用可能なメモリ以上に消費しない**ようにする必要があります。
生成されたプロジェクトはデプロイの指示がありますが、それを実行するとさらに2分かかります。
## 開始前の事前ステップとコンテナ
コンテナDockerやKubernetesなどを使っている場合、主に2つのアプローチがあります。
### 複数のコンテナ
複数の**コンテナ**があり、おそらくそれぞれが**単一のプロセス**を実行している場合(**Kubernetes**クラスタなど)、レプリケートされたワーカーコンテナを実行する**前に**、単一のコンテナで**事前のステップ**の作業を行う**別のコンテナ**を持ちたいと思うでしょう。
!!! info
もしKubernetesを使用している場合, これはおそらく<a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init コンテナ</a>でしょう。
ユースケースが事前のステップを**並列で複数回**実行するのに問題がない場合(例:データベースの準備チェック)、メインプロセスを開始する前に、それらのステップを各コンテナに入れることが可能です。
### 単一コンテナ
単純なセットアップで、**単一のコンテナ**で複数の**ワーカー・プロセス**または1つのプロセスのみを起動する場合、アプリでプロセスを開始する直前に、同じコンテナで事前のステップを実行できます。公式Dockerイメージは、内部的にこれをサポートしています。
## Gunicornによる公式Dockerイメージ - Uvicorn
前の章で詳しく説明したように、Uvicornワーカーで動作するGunicornを含む公式のDockerイメージがあります [Server Workers - Gunicorn と Uvicorn](./server-workers.md){.internal-link target=_blank}で詳しく説明しています。
このイメージは、主に上記で説明した状況で役に立つでしょう: [複数のプロセスと特殊なケースを持つコンテナContainers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases)
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
!!! warning
このベースイメージや類似のイメージは**必要ない**可能性が高いので、[上記の: FastAPI用のDockerイメージをビルドするBuild a Docker Image for FastAPI](#build-a-docker-image-for-fastapi)のようにゼロからイメージをビルドする方が良いでしょう。
このイメージには、利用可能なCPUコアに基づいて**ワーカー・プロセスの数**を設定する**オートチューニング**メカニズムが含まれています。
これは**賢明なデフォルト**を備えていますが、**環境変数**や設定ファイルを使ってすべての設定を変更したり更新したりすることができます。
また、スクリプトで<a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**開始前の事前ステップ**</a>を実行することもサポートしている。
!!! tip
すべての設定とオプションを見るには、Dockerイメージのページをご覧ください: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>
### 公式Dockerイメージのプロセス数
このイメージの**プロセス数**は、利用可能なCPU**コア**から**自動的に計算**されます。
つまり、CPUから可能な限り**パフォーマンス**を**引き出そう**とします。
また、**環境変数**などを使った設定で調整することもできます。
しかし、プロセスの数はコンテナが実行しているCPUに依存するため、**消費されるメモリの量**もそれに依存することになります。
そのため、機械学習モデルなどで大量のメモリを消費するアプリケーションで、サーバーのCPUコアが多いが**メモリが少ない**場合、コンテナは利用可能なメモリよりも多くのメモリを使おうとすることになります。
その結果、パフォーマンスが大幅に低下する(あるいはクラッシュする)可能性があります。🚨
### Dockerfileを作成する
この画像に基づいて`Dockerfile`を作成する方法を以下に示します:
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY ./app /app
```
### より大きなアプリケーション
[複数のファイルを持つ大きなアプリケーション](../tutorial/bigger-applications.md){.internal-link target=_blank}を作成するセクションに従った場合、`Dockerfile`は次のようになります:
```Dockerfile hl_lines="7"
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY ./app /app/app
```
### いつ使うのか
おそらく、**Kubernetes**(または他のもの)を使用していて、すでにクラスタレベルで複数の**コンテナ**で**レプリケーション**を設定している場合は、この公式ベースイメージ(または他の類似のもの)は**使用すべきではありません**。
そのような場合は、上記のように**ゼロから**イメージを構築する方がよいでしょう: [FastAPI用のDockerイメージをビルドするBuild a Docker Image for FastAPI](#build-a-docker-image-for-fastapi) を参照してください。
このイメージは、主に上記の[複数のプロセスと特殊なケースを持つコンテナContainers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases)で説明したような特殊なケースで役に立ちます。
例えば、アプリケーションが**シンプル**で、CPUに応じたデフォルトのプロセス数を設定すればうまくいく場合や、クラスタレベルでレプリケーションを手動で設定する手間を省きたい場合、アプリで複数のコンテナを実行しない場合などです。
または、**Docker Compose**でデプロイし、単一のサーバで実行している場合などです。
## コンテナ・イメージのデプロイ
コンテナDockerイメージを手に入れた後、それをデプロイするにはいくつかの方法があります。
例えば以下のリストの方法です:
* 単一サーバーの**Docker Compose**
* **Kubernetes**クラスタ
* Docker Swarmモードのクラスター
* Nomadのような別のツール
* コンテナ・イメージをデプロイするクラウド・サービス
## Poetryを利用したDockerイメージ
もしプロジェクトの依存関係を管理するために<a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a>を利用する場合、マルチステージビルドを使うと良いでしょう。
```{ .dockerfile .annotate }
# (1)
FROM python:3.9 as requirements-stage
# (2)
WORKDIR /tmp
# (3)
RUN pip install poetry
# (4)
COPY ./pyproject.toml ./poetry.lock* /tmp/
# (5)
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
# (6)
FROM python:3.9
# (7)
WORKDIR /code
# (8)
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
# (9)
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (10)
COPY ./app /code/app
# (11)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. これは最初のステージで、`requirements-stage`と名付けられます
2. `/tmp` を現在の作業ディレクトリに設定します
ここで `requirements.txt` というファイルを生成します。
3. このDockerステージにPoetryをインストールします
4. pyproject.toml``poetry.lock`ファイルを`/tmp` ディレクトリにコピーします
`./poetry.lock*`(末尾に`*`)を使用するため、そのファイルがまだ利用できない場合でもクラッシュすることはないです。
5. requirements.txt`ファイルを生成します
6. これは最後のステージであり、ここにあるものはすべて最終的なコンテナ・イメージに保存されます
7. 現在の作業ディレクトリを `/code` に設定します
8. `requirements.txt`ファイルを `/code` ディレクトリにコピーします
このファイルは前のDockerステージにしか存在しないため、`--from-requirements-stage`を使ってコピーします。
9. 生成された `requirements.txt` ファイルにあるパッケージの依存関係をインストールします
10. app` ディレクトリを `/code` ディレクトリにコピーします
11. uvicorn` コマンドを実行して、`app.main` からインポートした `app` オブジェクトを使用するように指示します
!!! tip
"+"の吹き出しをクリックすると、それぞれの行が何をするのかを見ることができます
**Dockerステージ**は`Dockerfile`の一部で、**一時的なコンテナイメージ**として動作します。
最初のステージは **Poetryのインストール**と Poetry の `pyproject.toml` ファイルからプロジェクトの依存関係を含む**`requirements.txt`を生成**するためだけに使用されます。
この `requirements.txt` ファイルは後半の **次のステージ**で `pip` と共に使用されます。
最終的なコンテナイメージでは、**最終ステージ**のみが保存されます。前のステージは破棄されます。
Poetryを使用する場合、**Dockerマルチステージビルド**を使用することは理にかなっています。
なぜなら、最終的なコンテナイメージにPoetryとその依存関係がインストールされている必要はなく、**必要なのは**プロジェクトの依存関係をインストールするために生成された `requirements.txt` ファイルだけだからです。
そして次の(そして最終的な)ステージでは、前述とほぼ同じ方法でイメージをビルドします。
### TLS Termination Proxyの裏側 - Poetry
繰り返しになりますが、NginxやTraefikのようなTLS Termination Proxyロードバランサーの後ろでコンテナを動かしている場合は、`--proxy-headers`オプションをコマンドに追加します:
```Dockerfile
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
```
## まとめ
コンテナ・システム(例えば**Docker**や**Kubernetes**など)を使えば、すべての**デプロイメントのコンセプト**を扱うのがかなり簡単になります:
* セキュリティ - HTTPS
* 起動時の実行
* 再起動
* **レプリケーション(実行中のプロセス数)**
* メモリ
* 開始前の事前ステップ
ほとんどの場合、ベースとなるイメージは使用せず、公式のPython Dockerイメージをベースにした**コンテナイメージをゼロからビルド**します。
`Dockerfile`と**Dockerキャッシュ**内の命令の**順番**に注意することで、**ビルド時間を最小化**することができ、生産性を最大化することができます(そして退屈を避けることができます)。😎
特別なケースでは、FastAPI用の公式Dockerイメージを使いたいかもしれません。🤓

View File

@@ -0,0 +1,182 @@
# Server Workers - Gunicorn と Uvicorn
前回のデプロイメントのコンセプトを振り返ってみましょう:
* セキュリティ - HTTPS
* 起動時の実行
* 再起動
* **レプリケーション(実行中のプロセス数)**
* メモリ
* 開始前の事前ステップ
ここまでのドキュメントのチュートリアルでは、おそらくUvicornのような**サーバープログラム**を**単一のプロセス**で実行しています。
アプリケーションをデプロイする際には、**複数のコア**を利用し、そしてより多くのリクエストを処理できるようにするために、プロセスの**レプリケーション**を持つことを望むでしょう。
前のチャプターである[デプロイメントのコンセプト](./concepts.md){.internal-link target=_blank}にて見てきたように、有効な戦略がいくつかあります。
ここでは<a href="https://gunicorn.org/" class="external-link" target="_blank">**Gunicorn**</a>が**Uvicornのワーカー・プロセス**を管理する場合の使い方について紹介していきます。
!!! info
<!-- NOTE: the current version of docker.md is outdated compared to English one. -->
DockerやKubernetesなどのコンテナを使用している場合は、次の章で詳しく説明します [コンテナ内のFastAPI - Docker](./docker.md){.internal-link target=_blank}
特に**Kubernetes**上で実行する場合は、おそらく**Gunicornを使用せず**、**コンテナごとに単一のUvicornプロセス**を実行することになりますが、それについてはこの章の後半で説明します。
## GunicornによるUvicornのワーカー・プロセスの管理
**Gunicorn**は**WSGI標準**のアプリケーションサーバーです。このことは、GunicornはFlaskやDjangoのようなアプリケーションにサービスを提供できることを意味します。Gunicornそれ自体は**FastAPI**と互換性がないですが、というのもFastAPIは最新の**<a href="https://asgi.readthedocs.io/en/latest/" class="external-link" target="_blank">ASGI 標準</a>**を使用しているためです。
しかし、Gunicornは**プロセスマネージャー**として動作し、ユーザーが特定の**ワーカー・プロセスクラス**を使用するように指示することができます。するとGunicornはそのクラスを使い1つ以上の**ワーカー・プロセス**を開始します。
そして**Uvicorn**には**Gunicorn互換のワーカークラス**があります。
この組み合わせで、Gunicornは**プロセスマネージャー**として動作し、**ポート**と**IP**をリッスンします。そして、**Uvicornクラス**を実行しているワーカー・プロセスに通信を**転送**します。
そして、Gunicorn互換の**Uvicornワーカー**クラスが、FastAPIが使えるように、Gunicornから送られてきたデータをASGI標準に変換する役割を担います。
## GunicornとUvicornをインストールする
<div class="termy">
```console
$ pip install "uvicorn[standard]" gunicorn
---> 100%
```
</div>
これによりUvicornと高性能を得るための標準`standard`の追加パッケージとGunicornの両方がインストールされます。
## UvicornのワーカーとともにGunicornを実行する
Gunicornを以下のように起動させることができます:
<div class="termy">
```console
$ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80
[19499] [INFO] Starting gunicorn 20.1.0
[19499] [INFO] Listening at: http://0.0.0.0:80 (19499)
[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker
[19511] [INFO] Booting worker with pid: 19511
[19513] [INFO] Booting worker with pid: 19513
[19514] [INFO] Booting worker with pid: 19514
[19515] [INFO] Booting worker with pid: 19515
[19511] [INFO] Started server process [19511]
[19511] [INFO] Waiting for application startup.
[19511] [INFO] Application startup complete.
[19513] [INFO] Started server process [19513]
[19513] [INFO] Waiting for application startup.
[19513] [INFO] Application startup complete.
[19514] [INFO] Started server process [19514]
[19514] [INFO] Waiting for application startup.
[19514] [INFO] Application startup complete.
[19515] [INFO] Started server process [19515]
[19515] [INFO] Waiting for application startup.
[19515] [INFO] Application startup complete.
```
</div>
それぞれのオプションの意味を見てみましょう:
* `main:app` `main`は"`main`"という名前のPythonモジュール、つまりファイル`main.py`を意味します。そして `app`**FastAPI** アプリケーションの変数名です。
* main:app`はPythonの`import`文と同じようなものだと想像できます:
```Python
from main import app
```
* つまり、`main:app`のコロンは、`from main import app`のPythonの`import`の部分と同じになります。
* `--workers` 使用するワーカー・プロセスの数で、それぞれがUvicornのワーカーを実行します。
* `--worker-class` ワーカー・プロセスで使用するGunicorn互換のワーカークラスです。
* ここではGunicornがインポートして使用できるクラスを渡します
```Python
import uvicorn.workers.UvicornWorker
```
* `--bind` GunicornにリッスンするIPとポートを伝えます。コロン(`:`)でIPとポートを区切ります。
* Uvicornを直接実行している場合は、`--bind 0.0.0.0:80` Gunicornのオプションの代わりに、`--host 0.0.0.0`と `--port 80`を使います。
出力では、各プロセスの**PID**プロセスIDが表示されているのがわかります単なる数字です
以下の通りです:
* Gunicornの**プロセス・マネージャー**はPID `19499`(あなたの場合は違う番号でしょう)で始まります。
* 次に、`Listening at: http://0.0.0.0:80`を開始します。
* それから `uvicorn.workers.UvicornWorker` でワーカークラスを使用することを検出します。
* そして、**4つのワーカー**を起動します。それぞれのワーカーのPIDは、`19511`、`19513`、`19514`、`19515`です。
Gunicornはまた、ワーカーの数を維持するために必要であれば、**ダウンしたプロセス**を管理し、**新しいプロセスを**再起動**させます。そのため、上記のリストにある**再起動**の概念に一部役立ちます。
しかしながら、必要であればGunicornを**再起動**させ、**起動時に実行**させるなど、外部のコンポーネントを持たせることも必要かもしれません。
## Uvicornとワーカー
Uvicornには複数の**ワーカー・プロセス**を起動し実行するオプションもあります。
とはいうものの、今のところUvicornのワーカー・プロセスを扱う機能はGunicornよりも制限されています。そのため、このレベルPythonレベルでプロセスマネージャーを持ちたいのであれば、Gunicornをプロセスマネージャーとして使ってみた方が賢明かもしれないです。
どんな場合であれ、以下のように実行します:
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
<font color="#A6E22E">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8080</b> (Press CTRL+C to quit)
<font color="#A6E22E">INFO</font>: Started parent process [<font color="#A1EFE4"><b>27365</b></font>]
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27368</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27369</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27370</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27367</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
```
</div>
ここで唯一の新しいオプションは `--workers` で、Uvicornに4つのワーカー・プロセスを起動するように指示しています。
各プロセスの **PID** が表示され、親プロセスの `27365` (これは **プロセスマネージャ**) と、各ワーカー・プロセスの **PID** が表示されます: `27368`、`27369`、`27370`、`27367`になります。
## デプロイメントのコンセプト
ここでは、アプリケーションの実行を**並列化**し、CPUの**マルチコア**を活用し、**より多くのリクエスト**に対応できるようにするために、**Gunicorn**またはUvicornを使用して**Uvicornワーカー・プロセス**を管理する方法を見ていきました。
上記のデプロイのコンセプトのリストから、ワーカーを使うことは主に**レプリケーション**の部分と、**再起動**を少し助けてくれます:
* セキュリティ - HTTPS
* 起動時の実行
* 再起動
* レプリケーション(実行中のプロセス数)
* メモリー
* 開始前の事前のステップ
## コンテナとDocker
<!-- NOTE: the current version of docker.md is outdated compared to English one. -->
次章の[コンテナ内のFastAPI - Docker](./docker.md){.internal-link target=_blank}では、その他の**デプロイのコンセプト**を扱うために実施するであろう戦略をいくつか紹介します。
また、**GunicornとUvicornワーカー**を含む**公式Dockerイメージ**と、簡単なケースに役立ついくつかのデフォルト設定も紹介します。
また、(Gunicornを使わずに)Uvicornプロセスを1つだけ実行するために、**ゼロから独自のイメージを**構築する方法も紹介します。これは簡単なプロセスで、おそらく**Kubernetes**のような分散コンテナ管理システムを使うときにやりたいことでしょう。
## まとめ
Uvicornワーカーを使ったプロセスマネージャとして**Gunicorn**またはUvicornを使えば、**マルチコアCPU**を活用して**複数のプロセスを並列実行**できます。
これらのツールやアイデアは、**あなた自身のデプロイシステム**をセットアップしながら、他のデプロイコンセプトを自分で行う場合にも使えます。
次の章では、コンテナDockerやKubernetesなどを使った**FastAPI**について学んでいきましょう。これらのツールには、他の**デプロイのコンセプト**も解決する簡単な方法があることがわかるでしょう。✨

View File

@@ -0,0 +1,700 @@
# FastAPI и Docker-контейнеры
При развёртывании приложений FastAPI, часто начинают с создания **образа контейнера на основе Linux**. Обычно для этого используют <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Затем можно развернуть такой контейнер на сервере одним из нескольких способов.
Использование контейнеров на основе Linux имеет ряд преимуществ, включая **безопасность**, **воспроизводимость**, **простоту** и прочие.
!!! tip "Подсказка"
Торопитесь или уже знакомы с этой технологией? Перепрыгните на раздел [Создать Docker-образ для FastAPI 👇](#docker-fastapi)
<details>
<summary>Развернуть Dockerfile 👀</summary>
```Dockerfile
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
# Если используете прокси-сервер, такой как Nginx или Traefik, добавьте --proxy-headers
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
```
</details>
## Что такое "контейнер"
Контейнеризация - это **легковесный** способ упаковать приложение, включая все его зависимости и необходимые файлы, чтобы изолировать его от других контейнеров (других приложений и компонентов) работающих на этой же системе.
Контейнеры, основанные на Linux, запускаются используя ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это значит, что они очень легковесные (по сравнению с полноценными виртуальными машинами, полностью эмулирующими работу операционной системы).
Благодаря этому, контейнеры потребляют **малое количество ресурсов**, сравнимое с процессом запущенным напрямую (виртуальная машина потребует гораздо больше ресурсов).
Контейнеры также имеют собственные запущенные **изолированные** процессы (но часто только один процесс), файловую систему и сеть, что упрощает развёртывание, разработку, управление доступом и т.п.
## Что такое "образ контейнера"
Для запуска **контейнера** нужен **образ контейнера**.
Образ контейнера - это **замороженная** версия всех файлов, переменных окружения, программ и команд по умолчанию, необходимых для работы приложения. **Замороженный** - означает, что **образ** не запущен и не выполняется, это всего лишь упакованные вместе файлы и метаданные.
В отличие от **образа контейнера**, хранящего неизменное содержимое, под термином **контейнер** подразумевают запущенный образ, то есть объёкт, который **исполняется**.
Когда **контейнер** запущен (на основании **образа**), он может создавать и изменять файлы, переменные окружения и т.д. Эти изменения будут существовать только внутри контейнера, но не будут сохраняться в образе контейнера (не будут сохранены на диск).
Образ контейнера можно сравнить с файлом, содержащем **программу**, например, как файл `main.py`.
И **контейнер** (в отличие от **образа**) - это на самом деле выполняемый экземпляр образа, примерно как **процесс**. По факту, контейнер запущен только когда запущены его процессы (чаще, всего один процесс) и остановлен, когда запущенных процессов нет.
## Образы контейнеров
Docker является одним оз основных инструментов для создания **образов** и **контейнеров** и управления ими.
Существует общедоступный <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> с подготовленными **официальными образами** многих инструментов, окружений, баз данных и приложений.
К примеру, есть официальный <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">образ Python</a>.
Также там представлены и другие полезные образы, такие как базы данных:
* <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>
и т.п.
Использование подготовленных образов значительно упрощает **комбинирование** и использование разных инструментов. Например, Вы можете попытаться использовать новую базу данных. В большинстве случаев можно использовать **официальный образ** и всего лишь указать переменные окружения.
Таким образом, Вы можете изучить, что такое контейнеризация и Docker, и использовать полученные знания с разными инструментами и компонентами.
Так, Вы можете запустить одновременно **множество контейнеров** с базой данных, Python-приложением, веб-сервером, React-приложением и соединить их вместе через внутреннюю сеть.
Все системы управления контейнерами (такие, как Docker или Kubernetes) имеют встроенные возможности для организации такого сетевого взаимодействия.
## Контейнеры и процессы
Обычно **образ контейнера** содержит метаданные предустановленной программы или команду, которую следует выполнить при запуске **контейнера**. Также он может содержать параметры, передаваемые предустановленной программе. Похоже на то, как если бы Вы запускали такую программу через терминал.
Когда **контейнер** запущен, он будет выполнять прописанные в нём команды и программы. Но Вы можете изменить его так, чтоб он выполнял другие команды и программы.
Контейнер буде работать до тех пор, пока выполняется его **главный процесс** (команда или программа).
В контейнере обычно выполняется **только один процесс**, но от его имени можно запустить другие процессы, тогда в этом же в контейнере будет выполняться **множество процессов**.
Контейнер не считается запущенным, если в нём **не выполняется хотя бы один процесс**. Если главный процесс остановлен, значит и контейнер остановлен.
## Создать Docker-образ для FastAPI
Что ж, давайте ужё создадим что-нибудь! 🚀
Я покажу Вам, как собирать **Docker-образ** для FastAPI **с нуля**, основываясь на **официальном образе Python**.
Такой подход сгодится для **большинства случаев**, например:
* Использование с **Kubernetes** или аналогичным инструментом
* Запуск в **Raspberry Pi**
* Использование в облачных сервисах, запускающих образы контейнеров для Вас и т.п.
### Установить зависимости
Обычно Вашему приложению необходимы **дополнительные библиотеки**, список которых находится в отдельном файле.
На название и содержание такого файла влияет выбранный Вами инструмент **установки** этих библиотек (зависимостей).
Чаще всего это простой файл `requirements.txt` с построчным перечислением библиотек и их версий.
При этом Вы, для выбора версий, будете использовать те же идеи, что упомянуты на странице [О версиях FastAPI](./versions.md){.internal-link target=_blank}.
Ваш файл `requirements.txt` может выглядеть как-то так:
```
fastapi>=0.68.0,<0.69.0
pydantic>=1.8.0,<2.0.0
uvicorn>=0.15.0,<0.16.0
```
Устанавливать зависимости проще всего с помощью `pip`:
<div class="termy">
```console
$ pip install -r requirements.txt
---> 100%
Successfully installed fastapi pydantic uvicorn
```
</div>
!!! info "Информация"
Существуют и другие инструменты управления зависимостями.
В этом же разделе, но позже, я покажу Вам пример использования Poetry. 👇
### Создать приложение **FastAPI**
* Создайте директорию `app` и перейдите в неё.
* Создайте пустой файл `__init__.py`.
* Создайте файл `main.py` и заполните его:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
### Dockerfile
В этой же директории создайте файл `Dockerfile` и заполните его:
```{ .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 ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. Начните с официального образа Python, который будет основой для образа приложения.
2. Укажите, что в дальнейшем команды запускаемые в контейнере, будут выполняться в директории `/code`.
Инструкция создаст эту директорию внутри контейнера и мы поместим в неё файл `requirements.txt` и директорию `app`.
3. Скопируете файл с зависимостями из текущей директории в `/code`.
Сначала копируйте **только** файл с зависимостями.
Этот файл **изменяется довольно редко**, Docker ищет изменения при постройке образа и если не находит, то использует **кэш**, в котором хранятся предыдущии версии сборки образа.
4. Установите библиотеки перечисленные в файле с зависимостями.
Опция `--no-cache-dir` указывает `pip` не сохранять загружаемые библиотеки на локальной машине для использования их в случае повторной загрузки. В контейнере, в случае пересборки этого шага, они всё равно будут удалены.
!!! note "Заметка"
Опция `--no-cache-dir` нужна только для `pip`, она никак не влияет на Docker или контейнеры.
Опция `--upgrade` указывает `pip` обновить библиотеки, емли они уже установлены.
Ка и в предыдущем шаге с копированием файла, этот шаг также будет использовать **кэш Docker** в случае отсутствия изменений.
Использрвание кэша, особенно на этом шаге, позволит Вам **сэкономить** кучу времени при повторной сборке образа, так как зависимости будут сохранены в кеше, а не **загружаться и устанавливаться каждый раз**.
5. Скопируйте директорию `./app` внутрь директории `/code` (в контейнере).
Так как в этой директории расположен код, который **часто изменяется**, то использование **кэша** на этом шаге будет наименее эффективно, а значит лучше поместить этот шаг **ближе к концу** `Dockerfile`, дабы не терять выгоду от оптимизации предыдущих шагов.
6. Укажите **команду**, запускающую сервер `uvicorn`.
`CMD` принимает список строк, разделённых запятыми, но при выполнении объединит их через пробел, собрав из них одну команду, которую Вы могли бы написать в терминале.
Эта команда будет выполнена в **текущей рабочей директории**, а именно в директории `/code`, котоая указана командой `WORKDIR /code`.
Так как команда выполняется внутрии директории `/code`, в которую мы поместили папку `./app` с приложением, то **Uvicorn** сможет найти и **импортировать** объект `app` из файла `app.main`.
!!! tip "Подсказка"
Если ткнёте на кружок с плюсом, то увидите пояснения. 👆
На данном этапе структура проекта должны выглядеть так:
```
.
├── app
│   ├── __init__.py
│ └── main.py
├── Dockerfile
└── requirements.txt
```
#### Использование прокси-сервера
Если Вы запускаете контейнер за прокси-сервером завершения TLS (балансирующего нагрузку), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`, которая укажет Uvicorn, что он работает позади прокси-сервера и может доверять заголовкам отправляемым им.
```Dockerfile
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
```
#### Кэш Docker'а
В нашем `Dockerfile` использована полезная хитрость, когда сначала копируется **только файл с зависимостями**, а не вся папка с кодом приложения.
```Dockerfile
COPY ./requirements.txt /code/requirements.txt
```
Docker и подобные ему инструменты **создают** образы контейнеров **пошагово**, добавляя **один слой над другим**, начиная с первой строки `Dockerfile` и добавляя файлы, создаваемые при выполнении каждой инструкции из `Dockerfile`.
При создании образа используется **внутренний кэш** и если в файлах нет изменений с момента последней сборки образа, то будет **переиспользован** ранее созданный слой образа, а не повторное копирование файлов и создание слоя с нуля.
Заметьте, что так как слой следующего шага зависит от слоя предыдущего, то изменения внесённые в промежуточный слой, также повлияют на последующие.
Избегание копирования файлов не обязательно улучшит ситуацию, но использование кэша на одном шаге, позволит **использовать кэш и на следующих шагах**. Например, можно использовать кэш при установке зависимостей:
```Dockerfile
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
```
Файл со списком зависимостей **изменяется довольно редко**. Так что выполнив команду копирования только этого файла, Docker сможет **использовать кэш** на этом шаге.
А затем **использовать кэш и на следующем шаге**, загружающем и устанавливающем зависимости. И вот тут-то мы и **сэкономим много времени**. ✨ ...а не будем томиться в тягостном ожидании. 😪😆
Для загрузки и установки необходимых библиотек **может понадобиться несколько минут**, но использование **кэша** занимает несколько **секунд** максимум.
И так как во время разработки Вы будете часто пересобирать контейнер для проверки работоспособности внесённых изменений, то сэкономленные минуты сложатся в часы, а то и дни.
Так как папка с кодом приложения **изменяется чаще всего**, то мы расположили её в конце `Dockerfile`, ведь после внесённых в код изменений кэш не будет использован на этом и следующих шагах.
```Dockerfile
COPY ./app /code/app
```
### Создать Docker-образ
Теперь, когда все файлы на своих местах, давайте создадим образ контейнера.
* Перейдите в директорию проекта (в ту, где расположены `Dockerfile` и папка `app` с приложением).
* Создай образ приложения FastAPI:
<div class="termy">
```console
$ docker build -t myimage .
---> 100%
```
</div>
!!! tip "Подсказка"
Обратите внимание, что в конце написана точка - `.`, это то же самое что и `./`, тем самым мы указываем Docker директорию, из которой нужно выполнять сборку образа контейнера.
В данном случае это та же самая директория (`.`).
### Запуск Docker-контейнера
* Запустите контейнер, основанный на Вашем образе:
<div class="termy">
```console
$ docker run -d --name mycontainer -p 80:80 myimage
```
</div>
## Проверка
Вы можете проверить, что Ваш Docker-контейнер работает перейдя по ссылке: <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> или <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> (или похожей, которую использует Ваш Docker-хост).
Там Вы увидите:
```JSON
{"item_id": 5, "q": "somequery"}
```
## Интерактивная документация API
Теперь перейдите по ссылке <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> или <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (или похожей, которую использует Ваш Docker-хост).
Здесь Вы увидите автоматическую интерактивную документацию API (предоставляемую <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)
## Альтернативная документация API
Также Вы можете перейти по ссылке <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> or <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (или похожей, которую использует Ваш Docker-хост).
Здесь Вы увидите альтернативную автоматическую документацию API (предоставляемую <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)
## Создание Docker-образа на основе однофайлового приложения FastAPI
Если Ваше приложение FastAPI помещено в один файл, например, `main.py` и структура Ваших файлов похожа на эту:
```
.
├── Dockerfile
├── main.py
└── requirements.txt
```
Вам нужно изменить в `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 ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. Скопируйте непосредственно файл `main.py` в директорию `/code` (не указывайте `./app`).
2. При запуске Uvicorn укажите ему, что объект `app` нужно импортировать из файла `main` (вместо импортирования из `app.main`).
Настройте Uvicorn на использование `main` вместо `app.main` для импорта объекта `app`.
## Концепции развёртывания
Давайте вспомним о [Концепциях развёртывания](./concepts.md){.internal-link target=_blank} и применим их к контейнерам.
Контейнеры - это, в основном, инструмент упрощающий **сборку и развёртывание** приложения и они не обязыают к применению какой-то определённой **концепции развёртывания**, а значит мы можем выбирать нужную стратегию.
**Хорошая новость** в том, что независимо от выбранной стратегии, мы всё равно можем покрыть все концепции развёртывания. 🎉
Рассмотрим эти **концепции развёртывания** применительно к контейнерам:
* Использование более безопасного протокола HTTPS
* Настройки запуска приложения
* Перезагрузка приложения
* Запуск нескольких экземпляров приложения
* Управление памятью
* Использование перечисленных функций перед запуском приложения
## Использование более безопасного протокола HTTPS
Если мы определимся, что **образ контейнера** будет содержать только приложение FastAPI, то работу с HTTPS можно организовать **снаружи** контейнера при помощи другого инструмента.
Это может быть другой контейнер, в котором есть, например, <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, работающий с **HTTPS** и **самостоятельно** обновляющий **сертификаты**.
!!! tip "Подсказка"
Traefik совместим с Docker, Kubernetes и им подобными инструментами. Он очень прост в установке и настройке использования HTTPS для Ваших контейнеров.
В качестве альтернативы, работу с HTTPS можно доверить облачному провайдеру, если он предоставляет такую услугу.
## Настройки запуска и перезагрузки приложения
Обычно **запуском контейнера с приложением** занимается какой-то отдельный инструмент.
Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный провайдер** и т.п.
В большинстве случаев это простейшие настройки запуска и перезагрузки приложения (при падении). Например, команде запуска Docker-контейнера можно добавить опцию `--restart`.
Управление запуском и перезагрузкой приложений без использования контейнеров - весьма затруднительно. Но при **работе с контейнерами** - это всего лишь функционал доступный по умолчанию. ✨
## Запуск нескольких экземпляров приложения - Указание количества процессов
Если у Вас есть <abbr title="Несколько серверов настроенных для совместной работы.">кластер</abbr> машин под управлением **Kubernetes**, Docker Swarm Mode, Nomad или аналогичной сложной системой оркестрации контейнеров, скорее всего, вместо использования менеджера процессов (типа Gunicorn и его воркеры) в каждом контейнере, Вы захотите **управлять количеством запущенных экземпляров приложения** на **уровне кластера**.
В любую из этих систем управления контейнерами обычно встроен способ управления **количеством запущенных контейнеров** для распределения **нагрузки** от входящих запросов на **уровне кластера**.
В такой ситуации Вы, вероятно, захотите создать **образ Docker**, как [описано выше](#dockerfile), с установленными зависимостями и запускающий **один процесс Uvicorn** вместо того, чтобы запускать Gunicorn управляющий несколькими воркерами Uvicorn.
### Балансировщик нагрузки
Обычно при использовании контейнеров один компонент **прослушивает главный порт**. Это может быть контейнер содержащий **прокси-сервер завершения работы TLS** для работы с **HTTPS** или что-то подобное.
Поскольку этот компонент **принимает запросы** и равномерно **распределяет** их между компонентами, его также называют **балансировщиком нагрузки**.
!!! tip "Подсказка"
**Прокси-сервер завершения работы TLS** одновременно может быть **балансировщиком нагрузки**.
Система оркестрации, которую Вы используете для запуска и управления контейнерами, имеет встроенный инструмент **сетевого взаимодействия** (например, для передачи HTTP-запросов) между контейнерами с Вашими приложениями и **балансировщиком нагрузки** (который также может быть **прокси-сервером**).
### Один балансировщик - Множество контейнеров
При работе с **Kubernetes** или аналогичными системами оркестрации использование их внутреннней сети позволяет иметь один **балансировщик нагрузки**, который прослушивает **главный** порт и передаёт запросы **множеству запущенных контейнеров** с Вашими приложениями.
В каждом из контейнеров обычно работает **только один процесс** (например, процесс Uvicorn управляющий Вашим приложением FastAPI). Контейнеры могут быть **идентичными**, запущенными на основе одного и того же образа, но у каждого будут свои отдельные процесс, память и т.п. Таким образом мы получаем преимущества **распараллеливания** работы по **разным ядрам** процессора или даже **разным машинам**.
Система управления контейнерами с **балансировщиком нагрузки** будет **распределять запросы** к контейнерам с приложениями **по очереди**. То есть каждый запрос будет обработан одним из множества **одинаковых контейнеров** с одним и тем же приложением.
**Балансировщик нагрузки** может обрабатывать запросы к *разным* приложениям, расположенным в Вашем кластере (например, если у них разные домены или префиксы пути) и передавать запросы нужному контейнеру с требуемым приложением.
### Один процесс на контейнер
В этом варианте **в одном контейнере будет запущен только один процесс (Uvicorn)**, а управление изменением количества запущенных копий приложения происходит на уровне кластера.
Здесь **не нужен** менеджер процессов типа Gunicorn, управляющий процессами Uvicorn, или же Uvicorn, управляющий другими процессами Uvicorn. Достаточно **только одного процесса Uvicorn** на контейнер (но запуск нескольких процессов не запрещён).
Использование менеджера процессов (Gunicorn или Uvicorn) внутри контейнера только добавляет **излишнее усложнение**, так как управление следует осуществлять системой оркестрации.
### <a name="special-cases"></a>Множество процессов внутри контейнера для особых случаев</a>
Безусловно, бывают **особые случаи**, когда может понадобится внутри контейнера запускать **менеджер процессов Gunicorn**, управляющий несколькими **процессами Uvicorn**.
Для таких случаев Вы можете использовать **официальный Docker-образ** (прим. пер: - *здесь и далее на этой странице, если Вы встретите сочетание "официальный Docker-образ" без уточнений, то автор имеет в виду именно предоставляемый им образ*), где в качестве менеджера процессов используется **Gunicorn**, запускающий несколько **процессов Uvicorn** и некоторые настройки по умолчанию, автоматически устанавливающие количество запущенных процессов в зависимости от количества ядер Вашего процессора. Я расскажу Вам об этом подробнее тут: [Официальный Docker-образ со встроенными Gunicorn и Uvicorn](#docker-gunicorn-uvicorn).
Некоторые примеры подобных случаев:
#### Простое приложение
Вы можете использовать менеджер процессов внутри контейнера, если Ваше приложение **настолько простое**, что у Вас нет необходимости (по крайней мере, пока нет) в тщательных настройках количества процессов и Вам достаточно имеющихся настроек по умолчанию (если используется официальный Docker-образ) для запуска приложения **только на одном сервере**, а не в кластере.
#### Docker Compose
С помощью **Docker Compose** можно разворачивать несколько контейнеров на **одном сервере** (не кластере), но при это у Вас не будет простого способа управления количеством запущенных контейнеров с одновременным сохранением общей сети и **балансировки нагрузки**.
В этом случае можно использовать **менеджер процессов**, управляющий **несколькими процессами**, внутри **одного контейнера**.
#### Prometheus и прочие причины
У Вас могут быть и **другие причины**, когда использование **множества процессов** внутри **одного контейнера** будет проще, нежели запуск **нескольких контейнеров** с **единственным процессом** в каждом из них.
Например (в зависимости от конфигурации), у Вас могут быть инструменты подобные экспортёру Prometheus, которые должны иметь доступ к **каждому запросу** приходящему в контейнер.
Если у Вас будет **несколько контейнеров**, то Prometheus, по умолчанию, **при сборе метрик** получит их **только с одного контейнера**, который обрабатывает конкретный запрос, вместо **сбора метрик** со всех работающих контейнеров.
В таком случае может быть проще иметь **один контейнер** со **множеством процессов**, с нужным инструментом (таким как экспортёр Prometheus) в этом же контейнере и собирающем метрики со всех внутренних процессов этого контейнера.
---
Самое главное - **ни одно** из перечисленных правил не является **высеченным в камне** и Вы не обязаны слепо их повторять. Вы можете использовать эти идеи при **рассмотрении Вашего конкретного случая** и самостоятельно решать, какая из концепции подходит лучше:
* Использование более безопасного протокола HTTPS
* Настройки запуска приложения
* Перезагрузка приложения
* Запуск нескольких экземпляров приложения
* Управление памятью
* Использование перечисленных функций перед запуском приложения
## Управление памятью
При **запуске одного процесса на контейнер** Вы получаете относительно понятный, стабильный и ограниченный объём памяти, потребляемый одним контейнером.
Вы можете установить аналогичные ограничения по памяти при конфигурировании своей системы управления контейнерами (например, **Kubernetes**). Таким образом система сможет **изменять количество контейнеров** на **доступных ей машинах** приводя в соответствие количество памяти нужной контейнерам с количеством памяти доступной в кластере (наборе доступных машин).
Если у Вас **простенькое** приложение, вероятно у Вас не будет **необходимости** устанавливать жёсткие ограничения на выделяемую ему память. Но если приложение **использует много памяти** (например, оно использует модели **машинного обучения**), Вам следует проверить, как много памяти ему требуется и отрегулировать **количество контейнеров** запущенных на **каждой машине** (может быть даже добавить машин в кластер).
Если Вы запускаете **несколько процессов в контейнере**, то должны быть уверены, что эти процессы не **займут памяти больше**, чем доступно для контейнера.
## Подготовительные шаги при запуске контейнеров
Есть два основных подхода, которые Вы можете использовать при запуске контейнеров (Docker, Kubernetes и т.п.).
### Множество контейнеров
Когда Вы запускаете **множество контейнеров**, в каждом из которых работает **только один процесс** (например, в кластере **Kubernetes**), может возникнуть необходимость иметь **отдельный контейнер**, который осуществит **предварительные шаги перед запуском** остальных контейнеров (например, применяет миграции к базе данных).
!!! info "Информация"
При использовании Kubernetes, это может быть <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Инициализирующий контейнер</a>.
При отсутствии такой необходимости (допустим, не нужно применять миграции к базе данных, а только проверить, что она готова принимать соединения), Вы можете проводить такую проверку в каждом контейнере перед запуском его основного процесса и запускать все контейнеры **одновременно**.
### Только один контейнер
Если у Вас несложное приложение для работы которого достаточно **одного контейнера**, но в котором работает **несколько процессов** (или один процесс), то прохождение предварительных шагов можно осуществить в этом же контейнере до запуска основного процесса. Официальный Docker-образ поддерживает такие действия.
## Официальный Docker-образ с Gunicorn и Uvicorn
Я подготовил для Вас Docker-образ, в который включён Gunicorn управляющий процессами (воркерами) Uvicorn, в соответствии с концепциями рассмотренными в предыдущей главе: [Рабочие процессы сервера (воркеры) - Gunicorn совместно с Uvicorn](./server-workers.md){.internal-link target=_blank}.
Этот образ может быть полезен для ситуаций описанных тут: [Множество процессов внутри контейнера для особых случаев](#special-cases).
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
!!! warning "Предупреждение"
Скорее всего у Вас **нет необходимости** в использовании этого образа или подобного ему и лучше создать свой образ с нуля как описано тут: [Создать Docker-образ для FastAPI](#docker-fastapi).
В этом образе есть **автоматический** механизм подстройки для запуска **необходимого количества процессов** в соответствии с доступным количеством ядер процессора.
В нём установлены **разумные значения по умолчанию**, но можно изменять и обновлять конфигурацию с помощью **переменных окружения** или конфигурационных файлов.
Он также поддерживает прохождение <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**Подготовительных шагов при запуске контейнеров**</a> при помощи скрипта.
!!! tip "Подсказка"
Для просмотра всех возможных настроек перейдите на страницу этого Docker-образа: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
### Количество процессов в официальном Docker-образе
**Количество процессов** в этом образе **вычисляется автоматически** и зависит от доступного количества **ядер** центрального процессора.
Это означает, что он будет пытаться **выжать** из процессора как можно больше **производительности**.
Но Вы можете изменять и обновлять конфигурацию с помощью **переменных окружения** и т.п.
Поскольку количество процессов зависит от процессора, на котором работает контейнер, **объём потребляемой памяти** также будет зависеть от этого.
А значит, если Вашему приложению требуется много оперативной памяти (например, оно использует модели машинного обучения) и Ваш сервер имеет центральный процессор с большим количеством ядер, но **не слишком большим объёмом оперативной памяти**, то может дойти до того, что контейнер попытается занять памяти больше, чем доступно, из-за чего будет падение производительности (или сервер вовсе упадёт). 🚨
### Написание `Dockerfile`
Итак, теперь мы можем написать `Dockerfile` основанный на этом официальном Docker-образе:
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY ./app /app
```
### Большие приложения
Если Вы успели ознакомиться с разделом [Приложения содержащие много файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}, состоящие из множества файлов, Ваш Dockerfile может выглядеть так:
```Dockerfile hl_lines="7"
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY ./app /app/app
```
### Как им пользоваться
Если Вы используете **Kubernetes** (или что-то вроде того), скорее всего Вам **не нужно** использовать официальный Docker-образ (или другой похожий) в качестве основы, так как управление **количеством запущенных контейнеров** должно быть настроено на уровне кластера. В таком случае лучше **создать образ с нуля**, как описано в разделе Создать [Docker-образ для FastAPI](#docker-fastapi).
Официальный образ может быть полезен в отдельных случаях, описанных выше в разделе [Множество процессов внутри контейнера для особых случаев](#special-cases). Например, если Ваше приложение **достаточно простое**, не требует запуска в кластере и способно уместиться в один контейнер, то его настройки по умолчанию будут работать довольно хорошо. Или же Вы развертываете его с помощью **Docker Compose**, работаете на одном сервере и т. д
## Развёртывание образа контейнера
После создания образа контейнера существует несколько способов его развёртывания.
Например:
* С использованием **Docker Compose** при развёртывании на одном сервере
* С использованием **Kubernetes** в кластере
* С использованием режима Docker Swarm в кластере
* С использованием других инструментов, таких как Nomad
* С использованием облачного сервиса, который будет управлять разворачиванием Вашего контейнера
## Docker-образ и Poetry
Если Вы пользуетесь <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> для управления зависимостями Вашего проекта, то можете использовать многоэтапную сборку образа:
```{ .dockerfile .annotate }
# (1)
FROM python:3.9 as requirements-stage
# (2)
WORKDIR /tmp
# (3)
RUN pip install poetry
# (4)
COPY ./pyproject.toml ./poetry.lock* /tmp/
# (5)
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
# (6)
FROM python:3.9
# (7)
WORKDIR /code
# (8)
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
# (9)
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (10)
COPY ./app /code/app
# (11)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. Это первый этап, которому мы дадим имя `requirements-stage`.
2. Установите директорию `/tmp` в качестве рабочей директории.
В ней будет создан файл `requirements.txt`
3. На этом шаге установите Poetry.
4. Скопируйте файлы `pyproject.toml` и `poetry.lock` в директорию `/tmp`.
Поскольку название файла написано как `./poetry.lock*` (с `*` в конце), то ничего не сломается, если такой файл не будет найден.
5. Создайте файл `requirements.txt`.
6. Это второй (и последний) этап сборки, который и создаст окончательный образ контейнера.
7. Установите директорию `/code` в качестве рабочей.
8. Скопируйте файл `requirements.txt` в директорию `/code`.
Этот файл находится в образе, созданном на предыдущем этапе, которому мы дали имя requirements-stage, потому при копировании нужно написать `--from-requirements-stage`.
9. Установите зависимости, указанные в файле `requirements.txt`.
10. Скопируйте папку `app` в папку `/code`.
11. Запустите `uvicorn`, указав ему использовать объект `app`, расположенный в `app.main`.
!!! tip "Подсказка"
Если ткнёте на кружок с плюсом, то увидите объяснения, что происходит в этой строке.
**Этапы сборки Docker-образа** являются частью `Dockerfile` и работают как **временные образы контейнеров**. Они нужны только для создания файлов, используемых в дальнейших этапах.
Первый этап был нужен только для **установки Poetry** и **создания файла `requirements.txt`**, в которым прописаны зависимости Вашего проекта, взятые из файла `pyproject.toml`.
На **следующем этапе** `pip` будет использовать файл `requirements.txt`.
В итоговом образе будет содержаться **только последний этап сборки**, предыдущие этапы будут отброшены.
При использовании Poetry, имеет смысл использовать **многоэтапную сборку Docker-образа**, потому что на самом деле Вам не нужен Poetry и его зависимости в окончательном образе контейнера, Вам **нужен только** сгенерированный файл `requirements.txt` для установки зависимостей Вашего проекта.
А на последнем этапе, придерживаясь описанных ранее правил, создаётся итоговый образ
### Использование прокси-сервера завершения TLS и Poetry
И снова повторюсь, если используете прокси-сервер (балансировщик нагрузки), такой, как Nginx или Traefik, добавьте в команду запуска опцию `--proxy-headers`:
```Dockerfile
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
```
## Резюме
При помощи систем контейнеризации (таких, как **Docker** и **Kubernetes**), становится довольно просто обрабатывать все **концепции развертывания**:
* Использование более безопасного протокола HTTPS
* Настройки запуска приложения
* Перезагрузка приложения
* Запуск нескольких экземпляров приложения
* Управление памятью
* Использование перечисленных функций перед запуском приложения
В большинстве случаев Вам, вероятно, не нужно использовать какой-либо базовый образ, **лучше создать образ контейнера с нуля** на основе официального Docker-образа Python.
Позаботившись о **порядке написания** инструкций в `Dockerfile`, Вы сможете использовать **кэш Docker'а**, **минимизировав время сборки**, максимально повысив свою производительность (и избежать скуки). 😎
В некоторых особых случаях вы можете использовать официальный образ Docker для FastAPI. 🤓

View File

@@ -0,0 +1,34 @@
# Глобальные зависимости
Для некоторых типов приложений может потребоваться добавить зависимости ко всему приложению.
Подобно тому, как вы можете [добавлять зависимости через параметр `dependencies` в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, вы можете добавлять зависимости сразу ко всему `FastAPI` приложению.
В этом случае они будут применяться ко всем *операциям пути* в приложении:
=== "Python 3.9+"
```Python hl_lines="16"
{!> ../../../docs_src/dependencies/tutorial012_an_py39.py!}
```
=== "Python 3.6+"
```Python hl_lines="16"
{!> ../../../docs_src/dependencies/tutorial012_an.py!}
```
=== "Python 3.6 non-Annotated"
!!! tip "Подсказка"
Рекомендуется использовать 'Annotated' версию, если это возможно.
```Python hl_lines="15"
{!> ../../../docs_src/dependencies/tutorial012.py!}
```
Все способы [добавления зависимостей в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения.
## Зависимости для групп *операций пути*
Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр dependencies для целой группы *операций пути*.

View File

@@ -0,0 +1,101 @@
# Настройка авторизации
Существует множество способов обеспечения безопасности, аутентификации и авторизации.
Обычно эта тема является достаточно сложной и трудной.
Во многих фреймворках и системах только работа с определением доступов к приложению и аутентификацией требует значительных затрат усилий и написания множества кода (во многих случаях его объём может составлять более 50% от всего написанного кода).
**FastAPI** предоставляет несколько инструментов, которые помогут вам настроить **Авторизацию** легко, быстро, стандартным способом, без необходимости изучать все её тонкости.
Но сначала давайте рассмотрим некоторые небольшие концепции.
## Куда-то торопишься?
Если вам не нужна информация о каких-либо из следующих терминов и вам просто нужно добавить защиту с аутентификацией на основе логина и пароля *прямо сейчас*, переходите к следующим главам.
## OAuth2
OAuth2 - это протокол, который определяет несколько способов обработки аутентификации и авторизации.
Он довольно обширен и охватывает несколько сложных вариантов использования.
OAuth2 включает в себя способы аутентификации с использованием "третьей стороны".
Это то, что используют под собой все кнопки "вход с помощью Facebook, Google, Twitter, GitHub" на страницах авторизации.
### OAuth 1
Ранее использовался протокол OAuth 1, который сильно отличается от OAuth2 и является более сложным, поскольку он включал прямые описания того, как шифровать сообщение.
В настоящее время он не очень популярен и не используется.
OAuth2 не указывает, как шифровать сообщение, он ожидает, что ваше приложение будет обслуживаться по протоколу HTTPS.
!!! tip "Подсказка"
В разделе **Развертывание** вы увидите [как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt.](https://fastapi.tiangolo.com/ru/deployment/https/)
## OpenID Connect
OpenID Connect - это еще один протокол, основанный на **OAuth2**.
Он просто расширяет OAuth2, уточняя некоторые вещи, не имеющие однозначного определения в OAuth2, в попытке сделать его более совместимым.
Например, для входа в Google используется OpenID Connect (который под собой использует OAuth2).
Но вход в Facebook не поддерживает OpenID Connect. У него есть собственная вариация OAuth2.
### OpenID (не "OpenID Connect")
Также ранее использовался стандарт "OpenID", который пытался решить ту же проблему, что и **OpenID Connect**, но не был основан на OAuth2.
Таким образом, это была полноценная дополнительная система.
В настоящее время не очень популярен и не используется.
## OpenAPI
OpenAPI (ранее известный как Swagger) - это открытая спецификация для создания API (в настоящее время является частью Linux Foundation).
**FastAPI** основан на **OpenAPI**.
Это то, что делает возможным наличие множества автоматических интерактивных интерфейсов документирования, сгенерированного кода и т.д.
В OpenAPI есть способ использовать несколько "схем" безопасности.
Таким образом, вы можете воспользоваться преимуществами Всех этих стандартных инструментов, включая интерактивные системы документирования.
OpenAPI может использовать следующие схемы авторизации:
* `apiKey`: уникальный идентификатор для приложения, который может быть получен из:
* Параметров запроса.
* Заголовка.
* Cookies.
* `http`: стандартные системы аутентификации по протоколу HTTP, включая:
* `bearer`: заголовок `Authorization` со значением `Bearer {уникальный токен}`. Это унаследовано от OAuth2.
* Базовая аутентификация по протоколу HTTP.
* HTTP Digest и т.д.
* `oauth2`: все способы обеспечения безопасности OAuth2 называемые "потоки" (англ. "flows").
* Некоторые из этих "потоков" подходят для реализации аутентификации через сторонний сервис использующий OAuth 2.0 (например, Google, Facebook, Twitter, GitHub и т.д.):
* `implicit`
* `clientCredentials`
* `authorizationCode`
* Но есть один конкретный "поток", который может быть идеально использован для обработки аутентификации непосредственно в том же приложении:
* `password`: в некоторых следующих главах будут рассмотрены примеры этого.
* `openIdConnect`: способ определить, как автоматически обнаруживать данные аутентификации OAuth2.
* Это автоматическое обнаружение определено в спецификации OpenID Connect.
!!! tip "Подсказка"
Интеграция сторонних сервисов для аутентификации/авторизации таких как Google, Facebook, Twitter, GitHub и т.д. осуществляется достаточно легко.
Самой сложной проблемой является создание такого провайдера аутентификации/авторизации, но **FastAPI** предоставляет вам инструменты, позволяющие легко это сделать, выполняя при этом всю тяжелую работу за вас.
## Преимущества **FastAPI**
Fast API предоставляет несколько инструментов для каждой из этих схем безопасности в модуле `fastapi.security`, которые упрощают использование этих механизмов безопасности.
В следующих главах вы увидите, как обезопасить свой API, используя инструменты, предоставляемые **FastAPI**.
И вы также увидите, как он автоматически интегрируется в систему интерактивной документации.

View File

@@ -0,0 +1,178 @@
# Люди FastAPI
FastAPI має дивовижну спільноту, яка вітає людей різного походження.
## Творець Супроводжувач
Привіт! 👋
Це я:
{% if people %}
<div class="user-list user-list-center">
{% for user in people.maintainers %}
<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">Answers: {{ user.answers }}</div><div class="count">Pull Requests: {{ user.prs }}</div></div>
{% endfor %}
</div>
{% endif %}
Я - творець і супроводжувач **FastAPI**. Детальніше про це можна прочитати в [Довідка FastAPI - Отримати довідку - Зв'язатися з автором](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}.
...Але тут я хочу показати вам спільноту.
---
**FastAPI** отримує велику підтримку від спільноти. І я хочу відзначити їхній внесок.
Це люди, які:
* [Допомагають іншим із проблемами (запитаннями) у GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}.
* [Створюють пул реквести](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}.
* Переглядають пул реквести, [особливо важливо для перекладів](contributing.md#translations){.internal-link target=_blank}.
Оплески їм. 👏 🙇
## Найбільш активні користувачі минулого місяця
Це користувачі, які [найбільше допомагали іншим із проблемами (запитаннями) у GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} протягом минулого місяця. ☕
{% if people %}
<div class="user-list user-list-center">
{% for user in people.last_month_active %}
<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">Issues replied: {{ user.count }}</div></div>
{% endfor %}
</div>
{% endif %}
## Експерти
Ось **експерти FastAPI**. 🤓
Це користувачі, які [найбільше допомагали іншим із проблемами (запитаннями) у GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} протягом *всього часу*.
Вони зарекомендували себе як експерти, допомагаючи багатьом іншим. ✨
{% if people %}
<div class="user-list user-list-center">
{% for user in people.experts %}
<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">Issues replied: {{ user.count }}</div></div>
{% endfor %}
</div>
{% endif %}
## Найкращі контрибютори
Ось **Найкращі контрибютори**. 👷
Ці користувачі [створили найбільшу кількість пул реквестів](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} які були *змержені*.
Вони надали програмний код, документацію, переклади тощо. 📦
{% if people %}
<div class="user-list user-list-center">
{% for user in people.top_contributors %}
<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">Pull Requests: {{ user.count }}</div></div>
{% endfor %}
</div>
{% endif %}
Є багато інших контрибюторів (більше сотні), їх усіх можна побачити на сторінці <a href="https://github.com/tiangolo/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors</a>. 👷
## Найкращі рецензенти
Ці користувачі є **Найкращими рецензентами**. 🕵️
### Рецензенти на переклади
Я розмовляю лише кількома мовами (і не дуже добре 😅). Отже, рецензенти це ті, хто має [**повноваження схвалювати переклади**](contributing.md#translations){.internal-link target=_blank} документації. Без них не було б документації кількома іншими мовами.
---
**Найкращі рецензенти** 🕵️ переглянули більшість пул реквестів від інших, забезпечуючи якість коду, документації і особливо **перекладів**.
{% if people %}
<div class="user-list user-list-center">
{% for user in people.top_reviewers %}
<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">Reviews: {{ user.count }}</div></div>
{% endfor %}
</div>
{% endif %}
## Спонсори
Це **Спонсори**. 😎
Вони підтримують мою роботу з **FastAPI** (та іншими), переважно через <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub Sponsors</a>.
{% if sponsors %}
{% if sponsors.gold %}
### Золоті спонсори
{% for sponsor in sponsors.gold -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor %}
{% endif %}
{% if sponsors.silver %}
### Срібні спонсори
{% 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 %}
{% if sponsors.bronze %}
### Бронзові спонсори
{% for sponsor in sponsors.bronze -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor %}
{% endif %}
{% endif %}
### Індивідуальні спонсори
{% if github_sponsors %}
{% for group in github_sponsors.sponsors %}
<div class="user-list user-list-center">
{% for user in group %}
{% if user.login not in sponsors_badge.logins %}
<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>
{% endif %}
{% endfor %}
</div>
{% endfor %}
{% endif %}
## Про дані - технічні деталі
Основна мета цієї сторінки висвітлити зусилля спільноти, щоб допомогти іншим.
Особливо враховуючи зусилля, які зазвичай менш помітні, а в багатьох випадках більш важкі, як-от допомога іншим із проблемами та перегляд пул реквестів перекладів.
Дані розраховуються щомісяця, ви можете ознайомитися з <a href="https://github.com/tiangolo/fastapi/blob/master/.github/actions/people/app/main.py" class="external-link" target="_blank">вихідним кодом тут</a>.
Тут я також підкреслюю внески спонсорів.
Я також залишаю за собою право оновлювати алгоритми підрахунку, види рейтингів, порогові значення тощо (про всяк випадок 🤷).

View File

@@ -0,0 +1,96 @@
# Параметри Cookie
Ви можете визначити параметри Cookie таким же чином, як визначаються параметри `Query` і `Path`.
## Імпорт `Cookie`
Спочатку імпортуйте `Cookie`:
=== "Python 3.10+"
```Python hl_lines="3"
{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!}
```
=== "Python 3.9+"
```Python hl_lines="3"
{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!}
```
=== "Python 3.6+"
```Python hl_lines="3"
{!> ../../../docs_src/cookie_params/tutorial001_an.py!}
```
=== "Python 3.10+ non-Annotated"
!!! tip
Бажано використовувати `Annotated` версію, якщо це можливо.
```Python hl_lines="1"
{!> ../../../docs_src/cookie_params/tutorial001_py310.py!}
```
=== "Python 3.6+ non-Annotated"
!!! tip
Бажано використовувати `Annotated` версію, якщо це можливо.
```Python hl_lines="3"
{!> ../../../docs_src/cookie_params/tutorial001.py!}
```
## Визначення параметрів `Cookie`
Потім визначте параметри cookie, використовуючи таку ж конструкцію як для `Path` і `Query`.
Перше значення це значення за замовчуванням, ви можете також передати всі додаткові параметри валідації чи анотації:
=== "Python 3.10+"
```Python hl_lines="9"
{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!}
```
=== "Python 3.9+"
```Python hl_lines="9"
{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!}
```
=== "Python 3.6+"
```Python hl_lines="10"
{!> ../../../docs_src/cookie_params/tutorial001_an.py!}
```
=== "Python 3.10+ non-Annotated"
!!! tip
Бажано використовувати `Annotated` версію, якщо це можливо.
```Python hl_lines="7"
{!> ../../../docs_src/cookie_params/tutorial001_py310.py!}
```
=== "Python 3.6+ non-Annotated"
!!! tip
Бажано використовувати `Annotated` версію, якщо це можливо.
```Python hl_lines="9"
{!> ../../../docs_src/cookie_params/tutorial001.py!}
```
!!! note "Технічні Деталі"
`Cookie` це "сестра" класів `Path` і `Query`. Вони наслідуються від одного батьківського класу `Param`.
Але пам'ятайте, що коли ви імпортуєте `Query`, `Path`, `Cookie` та інше з `fastapi`, це фактично функції, що повертають спеціальні класи.
!!! info
Для визначення cookies ви маєте використовувати `Cookie`, тому що в іншому випадку параметри будуть інтерпритовані, як параметри запиту.
## Підсумки
Визначайте cookies за допомогою `Cookie`, використовуючи той же спільний шаблон, що і `Query` та `Path`.

1
docs/uk/mkdocs.yml Normal file
View File

@@ -0,0 +1 @@
INHERIT: ../en/mkdocs.yml

View File

@@ -0,0 +1,52 @@
# بینچ مارکس
انڈیپنڈنٹ ٹیک امپور بینچ مارک **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 کے ذریعہ اندرونی طور پر استعمال کیا جاتا ہے ) (*)
لیکن جب بینچ مارک اور موازنہ کی جانچ پڑتال کرتے ہو تو آپ کو مندرجہ ذیل بات ذہن میں رکھنی چاہئے.
## بینچ مارک اور رفتار
جب آپ بینچ مارک کی جانچ کرتے ہیں تو ، مساوی کے مقابلے میں مختلف اقسام کے متعدد اوزار دیکھنا عام ہے.
خاص طور پر ، Uvicorn, Starlette اور FastAPI کو دیکھنے کے لئے ( بہت سے دوسرے ٹولز ) کے ساتھ موازنہ کیا گیا.
ٹول کے ذریعہ حل ہونے والا آسان مسئلہ ، اس کی بہتر کارکردگی ہوگی. اور زیادہ تر بینچ مارک ٹول کے ذریعہ فراہم کردہ اضافی خصوصیات کی جانچ نہیں کرتے ہیں.
درجہ بندی کی طرح ہے:
<ul style="direction: rtl;">
<li><div style="text-align: right;">ASGI :<b>Uvicorn</b> سرور</div></li>
<ul>
<li><div style="text-align: right;"><b>Starlette</b>: (Uvicorn استعمال کرتا ہے) ایک ویب مائیکرو فریم ورک </div></li>
<ul>
<li><div style="text-align: right;"><b>FastAPI</b>: (Starlette کا استعمال کرتا ہے) ایک API مائکرو فریم ورک جس میں APIs بنانے کے لیے کئی اضافی خصوصیات ہیں، ڈیٹا کی توثیق وغیرہ کے ساتھ۔</div></li>
</ul>
</ul>
</ul>
<ul style="direction: rtl;">
<li><div style="text-align: right;"><b>Uvicorn</b>:</div></li>
<ul>
<li><div style="text-align: right;">بہترین کارکردگی ہوگی، کیونکہ اس میں سرور کے علاوہ زیادہ اضافی کوڈ نہیں ہے۔</div></li>
<li><div style="text-align: right;">آپ براہ راست Uvicorn میں درخواست نہیں لکھیں گے۔ اس کا مطلب یہ ہوگا کہ آپ کے کوڈ میں کم و بیش، کم از کم، Starlette (یا <b>FastAPI</b>) کی طرف سے فراہم کردہ تمام کوڈ شامل کرنا ہوں گے۔ اور اگر آپ نے ایسا کیا تو، آپ کی حتمی ایپلیکیشن کا وہی اوور ہیڈ ہوگا جیسا کہ ایک فریم ورک استعمال کرنے اور آپ کے ایپ کوڈ اور کیڑے کو کم سے کم کرنا۔</div></li>
<li><div style="text-align: right;">اگر آپ Uvicorn کا موازنہ کر رہے ہیں تو اس کا موازنہ Daphne، Hypercorn، uWSGI وغیرہ ایپلیکیشن سرورز سے کریں۔</div></li>
</ul>
</ul>
<ul style="direction: rtl;">
<li><div style="text-align: right;"><b>Starlette</b>:</div></li>
<ul>
<li><div style="text-align: right;">Uvicorn کے بعد اگلی بہترین کارکردگی ہوگی۔ درحقیقت، Starlette چلانے کے لیے Uvicorn کا استعمال کرتی ہے۔ لہذا، یہ شاید زیادہ کوڈ پر عمل درآمد کرکے Uvicorn سے "سست" ہوسکتا ہے۔</div></li>
<li><div style="text-align: right;">لیکن یہ آپ کو آسان ویب ایپلیکیشنز بنانے کے لیے ٹولز فراہم کرتا ہے، راستوں پر مبنی روٹنگ کے ساتھ، وغیرہ۔</div></li>
<li><div style="text-align: right;">اگر آپ سٹارلیٹ کا موازنہ کر رہے ہیں تو اس کا موازنہ Sanic، Flask، Django وغیرہ سے کریں۔ ویب فریم ورکس (یا مائیکرو فریم ورکس)</div></li>
</ul>
</ul>
<ul style="direction: rtl;">
<li><div style="text-align: right;"><b>FastAPI</b>:</div></li>
<ul>
<li><div style="text-align: right;">جس طرح سے Uvicorn Starlette کا استعمال کرتا ہے اور اس سے تیز نہیں ہو سکتا، Starlette <b>FastAPI</b> کا استعمال کرتا ہے، اس لیے یہ اس سے تیز نہیں ہو سکتا۔</div></li>
<li><div style="text-align: right;">Starlette FastAPI کے اوپری حصے میں مزید خصوصیات فراہم کرتا ہے۔ وہ خصوصیات جن کی آپ کو APIs بناتے وقت تقریباً ہمیشہ ضرورت ہوتی ہے، جیسے ڈیٹا کی توثیق اور سیریلائزیشن۔ اور اسے استعمال کرنے سے، آپ کو خودکار دستاویزات مفت میں مل جاتی ہیں (خودکار دستاویزات چلنے والی ایپلی کیشنز میں اوور ہیڈ کو بھی شامل نہیں کرتی ہیں، یہ اسٹارٹ اپ پر تیار ہوتی ہیں)۔</div></li>
<li><div style="text-align: right;">اگر آپ نے FastAPI کا استعمال نہیں کیا ہے اور Starlette کو براہ راست استعمال کیا ہے (یا کوئی دوسرا ٹول، جیسے Sanic، Flask، Responder، وغیرہ) آپ کو تمام ڈیٹا کی توثیق اور سیریلائزیشن کو خود نافذ کرنا ہوگا۔ لہذا، آپ کی حتمی ایپلیکیشن اب بھی وہی اوور ہیڈ ہوگی جیسا کہ اسے FastAPI کا استعمال کرتے ہوئے بنایا گیا تھا۔ اور بہت سے معاملات میں، یہ ڈیٹا کی توثیق اور سیریلائزیشن ایپلی کیشنز میں لکھے گئے کوڈ کی سب سے بڑی مقدار ہے۔</div></li>
<li><div style="text-align: right;">لہذا، FastAPI کا استعمال کرکے آپ ترقیاتی وقت، Bugs، کوڈ کی لائنوں کی بچت کر رہے ہیں، اور شاید آپ کو وہی کارکردگی (یا بہتر) ملے گی اگر آپ اسے استعمال نہیں کرتے (جیسا کہ آپ کو یہ سب اپنے کوڈ میں لاگو کرنا ہوگا۔ )</div></li>
<li><div style="text-align: right;">اگر آپ FastAPI کا موازنہ کر رہے ہیں، تو اس کا موازنہ ویب ایپلیکیشن فریم ورک (یا ٹولز کے سیٹ) سے کریں جو ڈیٹا کی توثیق، سیریلائزیشن اور دستاویزات فراہم کرتا ہے، جیسے Flask-apispec، NestJS، Molten، وغیرہ۔ مربوط خودکار ڈیٹا کی توثیق، سیریلائزیشن اور دستاویزات کے ساتھ فریم ورک۔</div></li>
</ul>
</ul>

1
docs/ur/mkdocs.yml Normal file
View File

@@ -0,0 +1 @@
INHERIT: ../en/mkdocs.yml

View File

@@ -1,7 +1,3 @@
{!../../../docs/missing-translation.md!}
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>

View File

@@ -0,0 +1,545 @@
# Giới thiệu kiểu dữ liệu Python
Python hỗ trợ tùy chọn "type hints" (còn được gọi là "type annotations").
Những **"type hints"** hay chú thích là một cú pháp đặc biệt cho phép khai báo <abbr title="ví dụ: str, int, float, bool"> kiểu dữ liệu</abbr> của một biến.
Bằng việc khai báo kiểu dữ liệu cho các biến của bạn, các trình soạn thảo và các công cụ có thể hỗ trợ bạn tốt hơn.
Đây chỉ là một **hướng dẫn nhanh** về gợi ý kiểu dữ liệu trong Python. Nó chỉ bao gồm những điều cần thiết tối thiểu để sử dụng chúng với **FastAPI**... đó thực sự là rất ít.
**FastAPI** hoàn toàn được dựa trên những gợi ý kiểu dữ liệu, chúng mang đến nhiều ưu điểm và lợi ích.
Nhưng thậm chí nếu bạn không bao giờ sử dụng **FastAPI**, bạn sẽ được lợi từ việc học một ít về chúng.
!!! note
Nếu bạn là một chuyên gia về Python, và bạn đã biết mọi thứ về gợi ý kiểu dữ liệu, bỏ qua và đi tới chương tiếp theo.
## Động lực
Hãy bắt đầu với một ví dụ đơn giản:
```Python
{!../../../docs_src/python_types/tutorial001.py!}
```
Kết quả khi gọi chương trình này:
```
John Doe
```
Hàm thực hiện như sau:
* Lấy một `first_name``last_name`.
* Chuyển đổi kí tự đầu tiên của mỗi biến sang kiểu chữ hoa với `title()`.
* <abbr title="Đặt chúng lại với nhau thành một. Với các nội dung lần lượt.">Nối</abbr> chúng lại với nhau bằng một kí tự trắng ở giữa.
```Python hl_lines="2"
{!../../../docs_src/python_types/tutorial001.py!}
```
### Sửa đổi
Nó là một chương trình rất đơn giản.
Nhưng bây giờ hình dung rằng bạn đang viết nó từ đầu.
Tại một vài thời điểm, bạn sẽ bắt đầu định nghĩa hàm, bạn có các tham số...
Nhưng sau đó bạn phải gọi "phương thức chuyển đổi kí tự đầu tiên sang kiểu chữ hoa".
Có phải là `upper`? Có phải là `uppercase`? `first_uppercase`? `capitalize`?
Sau đó, bạn thử hỏi người bạn cũ của mình, autocompletion của trình soạn thảo.
Bạn gõ tham số đầu tiên của hàm, `first_name`, sau đó một dấu chấm (`.`) và sau đó ấn `Ctrl+Space` để kích hoạt bộ hoàn thành.
Nhưng đáng buồn, bạn không nhận được điều gì hữu ích cả:
<img src="/img/python-types/image01.png">
### Thêm kiểu dữ liệu
Hãy sửa một dòng từ phiên bản trước.
Chúng ta sẽ thay đổi chính xác đoạn này, tham số của hàm, từ:
```Python
first_name, last_name
```
sang:
```Python
first_name: str, last_name: str
```
Chính là nó.
Những thứ đó là "type hints":
```Python hl_lines="1"
{!../../../docs_src/python_types/tutorial002.py!}
```
Đó không giống như khai báo những giá trị mặc định giống như:
```Python
first_name="john", last_name="doe"
```
Nó là một thứ khác.
Chúng ta sử dụng dấu hai chấm (`:`), không phải dấu bằng (`=`).
Và việc thêm gợi ý kiểu dữ liệu không làm thay đổi những gì xảy ra so với khi chưa thêm chúng.
But now, imagine you are again in the middle of creating that function, but with type hints.
Tại cùng một điểm, bạn thử kích hoạt autocomplete với `Ctrl+Space` và bạn thấy:
<img src="/img/python-types/image02.png">
Với cái đó, bạn có thể cuộn, nhìn thấy các lựa chọn, cho đến khi bạn tìm thấy một "tiếng chuông":
<img src="/img/python-types/image03.png">
## Động lực nhiều hơn
Kiểm tra hàm này, nó đã có gợi ý kiểu dữ liệu:
```Python hl_lines="1"
{!../../../docs_src/python_types/tutorial003.py!}
```
Bởi vì trình soạn thảo biết kiểu dữ liệu của các biến, bạn không chỉ có được completion, bạn cũng được kiểm tra lỗi:
<img src="/img/python-types/image04.png">
Bây giờ bạn biết rằng bạn phải sửa nó, chuyển `age` sang một xâu với `str(age)`:
```Python hl_lines="2"
{!../../../docs_src/python_types/tutorial004.py!}
```
## Khai báo các kiểu dữ liệu
Bạn mới chỉ nhìn thấy những nơi chủ yếu để đặt khai báo kiểu dữ liệu. Như là các tham số của hàm.
Đây cũng là nơi chủ yếu để bạn sử dụng chúng với **FastAPI**.
### Kiểu dữ liệu đơn giản
Bạn có thể khai báo tất cả các kiểu dữ liệu chuẩn của Python, không chỉ là `str`.
Bạn có thể sử dụng, ví dụ:
* `int`
* `float`
* `bool`
* `bytes`
```Python hl_lines="1"
{!../../../docs_src/python_types/tutorial005.py!}
```
### Các kiểu dữ liệu tổng quát với tham số kiểu dữ liệu
Có một vài cấu trúc dữ liệu có thể chứa các giá trị khác nhau như `dict`, `list`, `set` và `tuple`. Và những giá trị nội tại cũng có thể có kiểu dữ liệu của chúng.
Những kiểu dữ liệu nội bộ này được gọi là những kiểu dữ liệu "**tổng quát**". Và có khả năng khai báo chúng, thậm chí với các kiểu dữ liệu nội bộ của chúng.
Để khai báo những kiểu dữ liệu và những kiểu dữ liệu nội bộ đó, bạn có thể sử dụng mô đun chuẩn của Python là `typing`. Nó có hỗ trợ những gợi ý kiểu dữ liệu này.
#### Những phiên bản mới hơn của Python
Cú pháp sử dụng `typing` **tương thích** với tất cả các phiên bản, từ Python 3.6 tới những phiên bản cuối cùng, bao gồm Python 3.9, Python 3.10,...
As Python advances, **những phiên bản mới** mang tới sự hỗ trợ được cải tiến cho những chú thích kiểu dữ liệu và trong nhiều trường hợp bạn thậm chí sẽ không cần import và sử dụng mô đun `typing` để khai báo chú thích kiểu dữ liệu.
Nếu bạn có thể chọn một phiên bản Python gần đây hơn cho dự án của bạn, ban sẽ có được những ưu điểm của những cải tiến đơn giản đó.
Trong tất cả các tài liệu tồn tại những ví dụ tương thích với mỗi phiên bản Python (khi có một sự khác nhau).
Cho ví dụ "**Python 3.6+**" có nghĩa là nó tương thích với Python 3.7 hoặc lớn hơn (bao gồm 3.7, 3.8, 3.9, 3.10,...). và "**Python 3.9+**" nghĩa là nó tương thích với Python 3.9 trở lên (bao gồm 3.10,...).
Nếu bạn có thể sử dụng **phiên bản cuối cùng của Python**, sử dụng những ví dụ cho phiên bản cuối, những cái đó sẽ có **cú pháp đơn giản và tốt nhât**, ví dụ, "**Python 3.10+**".
#### List
Ví dụ, hãy định nghĩa một biến là `list` các `str`.
=== "Python 3.9+"
Khai báo biến với cùng dấu hai chấm (`:`).
Tương tự kiểu dữ liệu `list`.
Như danh sách là một kiểu dữ liệu chứa một vài kiểu dữ liệu có sẵn, bạn đặt chúng trong các dấu ngoặc vuông:
```Python hl_lines="1"
{!> ../../../docs_src/python_types/tutorial006_py39.py!}
```
=== "Python 3.6+"
Từ `typing`, import `List` (với chữ cái `L` viết hoa):
``` Python hl_lines="1"
{!> ../../../docs_src/python_types/tutorial006.py!}
```
Khai báo biến với cùng dấu hai chấm (`:`).
Tương tự như kiểu dữ liệu, `List` bạn import từ `typing`.
Như danh sách là một kiểu dữ liệu chứa các kiểu dữ liệu có sẵn, bạn đặt chúng bên trong dấu ngoặc vuông:
```Python hl_lines="4"
{!> ../../../docs_src/python_types/tutorial006.py!}
```
!!! info
Các kiểu dữ liệu có sẵn bên trong dấu ngoặc vuông được gọi là "tham số kiểu dữ liệu".
Trong trường hợp này, `str` là tham số kiểu dữ liệu được truyền tới `List` (hoặc `list` trong Python 3.9 trở lên).
Có nghĩa là: "biến `items` là một `list`, và mỗi phần tử trong danh sách này là một `str`".
!!! tip
Nếu bạn sử dụng Python 3.9 hoặc lớn hơn, bạn không phải import `List` từ `typing`, bạn có thể sử dụng `list` để thay thế.
Bằng cách này, trình soạn thảo của bạn có thể hỗ trợ trong khi xử lí các phần tử trong danh sách:
<img src="/img/python-types/image05.png">
Đa phần đều không thể đạt được nếu không có các kiểu dữ liệu.
Chú ý rằng, biến `item` là một trong các phần tử trong danh sách `items`.
Và do vậy, trình soạn thảo biết nó là một `str`, và cung cấp sự hỗ trợ cho nó.
#### Tuple and Set
Bạn sẽ làm điều tương tự để khai báo các `tuple` và các `set`:
=== "Python 3.9+"
```Python hl_lines="1"
{!> ../../../docs_src/python_types/tutorial007_py39.py!}
```
=== "Python 3.6+"
```Python hl_lines="1 4"
{!> ../../../docs_src/python_types/tutorial007.py!}
```
Điều này có nghĩa là:
* Biến `items_t` là một `tuple` với 3 phần tử, một `int`, một `int` nữa, và một `str`.
* Biến `items_s` là một `set`, và mỗi phần tử của nó có kiểu `bytes`.
#### Dict
Để định nghĩa một `dict`, bạn truyền 2 tham số kiểu dữ liệu, phân cách bởi dấu phẩy.
Tham số kiểu dữ liệu đầu tiên dành cho khóa của `dict`.
Tham số kiểu dữ liệu thứ hai dành cho giá trị của `dict`.
=== "Python 3.9+"
```Python hl_lines="1"
{!> ../../../docs_src/python_types/tutorial008_py39.py!}
```
=== "Python 3.6+"
```Python hl_lines="1 4"
{!> ../../../docs_src/python_types/tutorial008.py!}
```
Điều này có nghĩa là:
* Biến `prices` là một `dict`:
* Khóa của `dict` này là kiểu `str` (đó là tên của mỗi vật phẩm).
* Giá trị của `dict` này là kiểu `float` (đó là giá của mỗi vật phẩm).
#### Union
Bạn có thể khai báo rằng một biến có thể là **một vài kiểu dữ liệu" bất kì, ví dụ, một `int` hoặc một `str`.
Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể sử dụng kiểu `Union` từ `typing` và đặt trong dấu ngoặc vuông những giá trị được chấp nhận.
In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</abbr>.
Trong Python 3.10 cũng có một **cú pháp mới** mà bạn có thể đặt những kiểu giá trị khả thi phân cách bởi một dấu <abbr title='cũng được gọi là "toán tử nhị phân"'>sổ dọc (`|`)</abbr>.
=== "Python 3.10+"
```Python hl_lines="1"
{!> ../../../docs_src/python_types/tutorial008b_py310.py!}
```
=== "Python 3.6+"
```Python hl_lines="1 4"
{!> ../../../docs_src/python_types/tutorial008b.py!}
```
Trong cả hai trường hợp có nghĩa là `item` có thể là một `int` hoặc `str`.
#### Khả năng `None`
Bạn có thể khai báo một giá trị có thể có một kiểu dữ liệu, giống như `str`, nhưng nó cũng có thể là `None`.
Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể khai báo nó bằng các import và sử dụng `Optional` từ mô đun `typing`.
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009.py!}
```
Sử dụng `Optional[str]` thay cho `str` sẽ cho phép trình soạn thảo giúp bạn phát hiện các lỗi mà bạn có thể gặp như một giá trị luôn là một `str`, trong khi thực tế nó rất có thể là `None`.
`Optional[Something]` là một cách viết ngắn gọn của `Union[Something, None]`, chúng là tương đương nhau.
Điều này cũng có nghĩa là trong Python 3.10, bạn có thể sử dụng `Something | None`:
=== "Python 3.10+"
```Python hl_lines="1"
{!> ../../../docs_src/python_types/tutorial009_py310.py!}
```
=== "Python 3.6+"
```Python hl_lines="1 4"
{!> ../../../docs_src/python_types/tutorial009.py!}
```
=== "Python 3.6+ alternative"
```Python hl_lines="1 4"
{!> ../../../docs_src/python_types/tutorial009b.py!}
```
#### Sử dụng `Union` hay `Optional`
If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view:
Nếu bạn đang sử dụng phiên bản Python dưới 3.10, đây là một mẹo từ ý kiến rất "chủ quan" của tôi:
* 🚨 Tránh sử dụng `Optional[SomeType]`
* Thay vào đó ✨ **sử dụng `Union[SomeType, None]`** ✨.
Cả hai là tương đương và bên dưới chúng giống nhau, nhưng tôi sẽ đễ xuất `Union` thay cho `Optional` vì từ "**tùy chọn**" có vẻ ngầm định giá trị là tùy chọn, và nó thực sự có nghĩa rằng "nó có thể là `None`", do đó nó không phải là tùy chọn và nó vẫn được yêu cầu.
Tôi nghĩ `Union[SomeType, None]` là rõ ràng hơn về ý nghĩa của nó.
Nó chỉ là về các từ và tên. Nhưng những từ đó có thể ảnh hưởng cách bạn và những đồng đội của bạn suy nghĩ về code.
Cho một ví dụ, hãy để ý hàm này:
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009c.py!}
```
Tham số `name` được định nghĩa là `Optional[str]`, nhưng nó **không phải là tùy chọn**, bạn không thể gọi hàm mà không có tham số:
```Python
say_hi() # Oh, no, this throws an error! 😱
```
Tham số `name` **vẫn được yêu cầu** (không phải là *tùy chọn*) vì nó không có giá trị mặc định. Trong khi đó, `name` chấp nhận `None` như là giá trị:
```Python
say_hi(name=None) # This works, None is valid 🎉
```
Tin tốt là, khi bạn sử dụng Python 3.10, bạn sẽ không phải lo lắng về điều đó, bạn sẽ có thể sử dụng `|` để định nghĩa hợp của các kiểu dữ liệu một cách đơn giản:
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009c_py310.py!}
```
Và sau đó, bạn sẽ không phải lo rằng những cái tên như `Optional` và `Union`. 😎
#### Những kiểu dữ liệu tổng quát
Những kiểu dữ liệu này lấy tham số kiểu dữ liệu trong dấu ngoặc vuông được gọi là **Kiểu dữ liệu tổng quát**, cho ví dụ:
=== "Python 3.10+"
Bạn có thể sử dụng các kiểu dữ liệu có sẵn như là kiểu dữ liệu tổng quát (với ngoặc vuông và kiểu dữ liệu bên trong):
* `list`
* `tuple`
* `set`
* `dict`
Và tương tự với Python 3.6, từ mô đun `typing`:
* `Union`
* `Optional` (tương tự như Python 3.6)
* ...và các kiểu dữ liệu khác.
Trong Python 3.10, thay vì sử dụng `Union` và `Optional`, bạn có thể sử dụng <abbr title='cũng gọi là "toán tử nhị phân", nhưng ý nghĩa không liên quan ở đây'>sổ dọc ('|')</abbr> để khai báo hợp của các kiểu dữ liệu, điều đó tốt hơn và đơn giản hơn nhiều.
=== "Python 3.9+"
Bạn có thể sử dụng các kiểu dữ liệu có sẵn tương tự như (với ngoặc vuông và kiểu dữ liệu bên trong):
* `list`
* `tuple`
* `set`
* `dict`
Và tương tự với Python 3.6, từ mô đun `typing`:
* `Union`
* `Optional`
* ...and others.
=== "Python 3.6+"
* `List`
* `Tuple`
* `Set`
* `Dict`
* `Union`
* `Optional`
* ...và các kiểu khác.
### Lớp như kiểu dữ liệu
Bạn cũng có thể khai báo một lớp như là kiểu dữ liệu của một biến.
Hãy nói rằng bạn muốn có một lớp `Person` với một tên:
```Python hl_lines="1-3"
{!../../../docs_src/python_types/tutorial010.py!}
```
Sau đó bạn có thể khai báo một biến có kiểu là `Person`:
```Python hl_lines="6"
{!../../../docs_src/python_types/tutorial010.py!}
```
Và lại một lần nữa, bạn có được tất cả sự hỗ trợ từ trình soạn thảo:
<img src="/img/python-types/image06.png">
Lưu ý rằng, điều này có nghĩa rằng "`one_person`" là một **thực thể** của lớp `Person`.
Nó không có nghĩa "`one_person`" là một **lớp** gọi là `Person`.
## Pydantic models
<a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> là một thư viện Python để validate dữ liệu hiệu năng cao.
Bạn có thể khai báo "hình dạng" của dữa liệu như là các lớp với các thuộc tính.
Và mỗi thuộc tính có một kiểu dữ liệu.
Sau đó bạn tạo một thực thể của lớp đó với một vài giá trị và nó sẽ validate các giá trị, chuyển đổi chúng sang kiểu dữ liệu phù hợp (nếu đó là trường hợp) và cho bạn một object với toàn bộ dữ liệu.
Và bạn nhận được tất cả sự hỗ trợ của trình soạn thảo với object kết quả đó.
Một ví dụ từ tài liệu chính thức của Pydantic:
=== "Python 3.10+"
```Python
{!> ../../../docs_src/python_types/tutorial011_py310.py!}
```
=== "Python 3.9+"
```Python
{!> ../../../docs_src/python_types/tutorial011_py39.py!}
```
=== "Python 3.6+"
```Python
{!> ../../../docs_src/python_types/tutorial011.py!}
```
!!! info
Để học nhiều hơn về <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic, tham khảo tài liệu của nó</a>.
**FastAPI** được dựa hoàn toàn trên Pydantic.
Bạn sẽ thấy nhiều ví dụ thực tế hơn trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}.
!!! tip
Pydantic có một hành vi đặc biệt khi bạn sử dụng `Optional` hoặc `Union[Something, None]` mà không có giá trị mặc dịnh, bạn có thể đọc nhiều hơn về nó trong tài liệu của Pydantic về <a href="https://pydantic-docs.helpmanual.io/usage/models/#required-optional-fields" class="external-link" target="_blank">Required Optional fields</a>.
## Type Hints với Metadata Annotations
Python cũng có một tính năng cho phép đặt **metadata bổ sung** trong những gợi ý kiểu dữ liệu này bằng cách sử dụng `Annotated`.
=== "Python 3.9+"
Trong Python 3.9, `Annotated` là một phần của thư viện chuẩn, do đó bạn có thể import nó từ `typing`.
```Python hl_lines="1 4"
{!> ../../../docs_src/python_types/tutorial013_py39.py!}
```
=== "Python 3.6+"
Ở phiên bản dưới Python 3.9, bạn import `Annotated` từ `typing_extensions`.
Nó đã được cài đặt sẵng cùng với **FastAPI**.
```Python hl_lines="1 4"
{!> ../../../docs_src/python_types/tutorial013.py!}
```
Python bản thân nó không làm bất kì điều gì với `Annotated`. Với các trình soạn thảo và các công cụ khác, kiểu dữ liệu vẫn là `str`.
Nhưng bạn có thể sử dụng `Annotated` để cung cấp cho **FastAPI** metadata bổ sung về cách mà bạn muốn ứng dụng của bạn xử lí.
Điều quan trọng cần nhớ là ***tham số kiểu dữ liệu* đầu tiên** bạn truyền tới `Annotated` là **kiểu giá trị thực sự**. Phần còn lại chỉ là metadata cho các công cụ khác.
Bây giờ, bạn chỉ cần biết rằng `Annotated` tồn tại, và nó là tiêu chuẩn của Python. 😎
Sau đó, bạn sẽ thấy sự **mạnh mẽ** mà nó có thể làm.
!!! tip
Thực tế, cái này là **tiêu chuẩn của Python**, nghĩa là bạn vẫn sẽ có được **trải nghiệm phát triển tốt nhất có thể** với trình soạn thảo của bạn, với các công cụ bạn sử dụng để phân tích và tái cấu trúc code của bạn, etc. ✨
Và code của bạn sẽ tương thích với nhiều công cụ và thư viện khác của Python. 🚀
## Các gợi ý kiểu dữ liệu trong **FastAPI**
**FastAPI** lấy các ưu điểm của các gợi ý kiểu dữ liệu để thực hiện một số thứ.
Với **FastAPI**, bạn khai báo các tham số với gợi ý kiểu và bạn có được:
* **Sự hỗ trợ từ các trình soạn thảo**.
* **Kiểm tra kiểu dữ liệu (type checking)**.
...và **FastAPI** sử dụng các khia báo để:
* **Định nghĩa các yêu cầu**: từ tham số đường dẫn của request, tham số query, headers, bodies, các phụ thuộc (dependencies),...
* **Chuyển dổi dữ liệu*: từ request sang kiểu dữ liệu được yêu cầu.
* **Kiểm tra tính đúng đắn của dữ liệu**: tới từ mỗi request:
* Sinh **lỗi tự động** để trả về máy khác khi dữ liệu không hợp lệ.
* **Tài liệu hóa** API sử dụng OpenAPI:
* cái mà sau được được sử dụng bởi tài liệu tương tác người dùng.
Điều này có thể nghe trừu tượng. Đừng lo lắng. Bạn sẽ thấy tất cả chúng trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}.
Điều quan trọng là bằng việc sử dụng các kiểu dữ liệu chuẩn của Python (thay vì thêm các lớp, decorators,...), **FastAPI** sẽ thực hiện nhiều công việc cho bạn.
!!! info
Nếu bạn đã đi qua toàn bộ các hướng dẫn và quay trở lại để tìm hiểu nhiều hơn về các kiểu dữ liệu, một tài nguyên tốt như <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">"cheat sheet" từ `mypy`</a>.

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.100.1"
__version__ = "0.101.1"
from starlette import status as status

View File

@@ -19,7 +19,7 @@ async def contextmanager_in_threadpool(
) -> AsyncGenerator[_T, None]:
# blocking __exit__ from running waiting on a free thread
# can create race conditions/deadlocks if the context manager itself
# has it's own internal pool (e.g. a database connection pool)
# has its own internal pool (e.g. a database connection pool)
# to avoid this we let __exit__ run without a capacity limit
# since we're creating a new limiter for each call, any non-zero limit
# works (1 is arbitrary)

View File

@@ -47,3 +47,9 @@ class ResponseValidationError(ValidationException):
def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
super().__init__(errors)
self.body = body
def __str__(self) -> str:
message = f"{len(self._errors)} validation errors:\n"
for err in self._errors:
message += f" {err}\n"
return message

View File

@@ -69,7 +69,7 @@ class Param(FieldInfo):
self.deprecated = deprecated
if example is not _Unset:
warnings.warn(
"`example` has been depreacated, please use `examples` instead",
"`example` has been deprecated, please use `examples` instead",
category=DeprecationWarning,
stacklevel=4,
)
@@ -98,7 +98,7 @@ class Param(FieldInfo):
kwargs["examples"] = examples
if regex is not None:
warnings.warn(
"`regex` has been depreacated, please use `pattern` instead",
"`regex` has been deprecated, please use `pattern` instead",
category=DeprecationWarning,
stacklevel=4,
)
@@ -512,7 +512,7 @@ class Body(FieldInfo):
self.deprecated = deprecated
if example is not _Unset:
warnings.warn(
"`example` has been depreacated, please use `examples` instead",
"`example` has been deprecated, please use `examples` instead",
category=DeprecationWarning,
stacklevel=4,
)

View File

@@ -83,7 +83,7 @@ def _prepare_response_content(
if read_with_orm_mode:
# Let from_orm extract the data from this model instead of converting
# it now to a dict.
# Otherwise there's no way to extract lazy data that requires attribute
# Otherwise, there's no way to extract lazy data that requires attribute
# access instead of dict iteration, e.g. lazy relationships.
return res
return _model_dump(
@@ -448,9 +448,7 @@ class APIRoute(routing.Route):
self.response_field = create_response_field(
name=response_name,
type_=self.response_model,
# TODO: This should actually set mode='serialization', just, that changes the schemas
# mode="serialization",
mode="validation",
mode="serialization",
)
# Create a clone of the field, so that a Pydantic submodel is not returned
# as is just because it's an instance of a subclass of a more limited class
@@ -458,7 +456,7 @@ class APIRoute(routing.Route):
# that doesn't have the hashed_password. But because it's a subclass, it
# would pass the validation and be returned as is.
# By being a new field, no inheritance will be passed as is. A new model
# will be always created.
# will always be created.
# TODO: remove when deprecating Pydantic v1
self.secure_cloned_response_field: Optional[
ModelField

View File

@@ -42,7 +42,7 @@ classifiers = [
]
dependencies = [
"starlette>=0.27.0,<0.28.0",
"pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,<3.0.0",
"pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0",
"typing-extensions>=4.5.0",
]
dynamic = ["version"]

View File

@@ -1,6 +1,5 @@
-e .
mkdocs==1.4.3
mkdocs-material==9.1.17
mkdocs-material==9.1.21
mdx-include >=1.4.1,<2.0.0
mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0
typer-cli >=0.0.13,<0.0.14

View File

@@ -2,7 +2,7 @@
pydantic-settings >=2.0.0
pytest >=7.1.3,<8.0.0
coverage[toml] >= 6.5.0,< 8.0
mypy ==1.4.0
mypy ==1.4.1
ruff ==0.0.275
black == 23.3.0
httpx >=0.23.0,<0.25.0

View File

@@ -0,0 +1,77 @@
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from .utils import needs_pydanticv2
@pytest.fixture(name="client")
def get_client():
app = FastAPI()
from pydantic import BaseModel, computed_field
class Rectangle(BaseModel):
width: int
length: int
@computed_field
@property
def area(self) -> int:
return self.width * self.length
@app.get("/")
def read_root() -> Rectangle:
return Rectangle(width=3, length=4)
client = TestClient(app)
return client
@needs_pydanticv2
def test_get(client: TestClient):
response = client.get("/")
assert response.status_code == 200, response.text
assert response.json() == {"width": 3, "length": 4, "area": 12}
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/": {
"get": {
"summary": "Read Root",
"operationId": "read_root__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Rectangle"}
}
},
}
},
}
}
},
"components": {
"schemas": {
"Rectangle": {
"properties": {
"width": {"type": "integer", "title": "Width"},
"length": {"type": "integer", "title": "Length"},
"area": {"type": "integer", "title": "Area", "readOnly": True},
},
"type": "object",
"required": ["width", "length", "area"],
"title": "Rectangle",
}
}
},
}

View File

@@ -1,7 +1,7 @@
from typing import Optional
import pytest
from dirty_equals import IsDict
from dirty_equals import HasRepr, IsDict, IsOneOf
from fastapi import Depends, FastAPI
from fastapi.exceptions import ResponseValidationError
from fastapi.testclient import TestClient
@@ -66,7 +66,7 @@ def test_validator_is_cloned(client: TestClient):
"loc": ("response", "name"),
"msg": "Value error, name must end in A",
"input": "modelX",
"ctx": {"error": "name must end in A"},
"ctx": {"error": HasRepr("ValueError('name must end in A')")},
"url": match_pydantic_error_url("value_error"),
}
)
@@ -139,7 +139,11 @@ def test_openapi_schema(client: TestClient):
},
"ModelA": {
"title": "ModelA",
"required": ["name", "foo"],
"required": IsOneOf(
["name", "description", "foo"],
# TODO remove when deprecating Pydantic v1
["name", "foo"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -247,8 +247,9 @@ def test_encode_model_with_pure_path():
class Config:
arbitrary_types_allowed = True
obj = ModelWithPath(path=PurePath("/foo", "bar"))
assert jsonable_encoder(obj) == {"path": "/foo/bar"}
test_path = PurePath("/foo", "bar")
obj = ModelWithPath(path=test_path)
assert jsonable_encoder(obj) == {"path": str(test_path)}
def test_encode_model_with_pure_posix_path():

View File

@@ -51,7 +51,7 @@ def test_jsonable_encoder_requiring_error():
"loc": ["body", 0, "age"],
"msg": "Input should be greater than 0",
"input": -1.0,
"ctx": {"gt": 0.0},
"ctx": {"gt": "0"},
"url": match_pydantic_error_url("greater_than"),
}
]
@@ -84,9 +84,23 @@ def test_put_incorrect_body_multiple():
"input": {"age": "five"},
"url": match_pydantic_error_url("missing"),
},
{
"ctx": {"class": "Decimal"},
"input": "five",
"loc": ["body", 0, "age", "is-instance[Decimal]"],
"msg": "Input should be an instance of Decimal",
"type": "is_instance_of",
"url": match_pydantic_error_url("is_instance_of"),
},
{
"type": "decimal_parsing",
"loc": ["body", 0, "age"],
"loc": [
"body",
0,
"age",
"function-after[to_decimal(), "
"union[int,constrained-str,function-plain[str()]]]",
],
"msg": "Input should be a valid decimal",
"input": "five",
},
@@ -97,9 +111,23 @@ def test_put_incorrect_body_multiple():
"input": {"age": "six"},
"url": match_pydantic_error_url("missing"),
},
{
"ctx": {"class": "Decimal"},
"input": "six",
"loc": ["body", 1, "age", "is-instance[Decimal]"],
"msg": "Input should be an instance of Decimal",
"type": "is_instance_of",
"url": match_pydantic_error_url("is_instance_of"),
},
{
"type": "decimal_parsing",
"loc": ["body", 1, "age"],
"loc": [
"body",
1,
"age",
"function-after[to_decimal(), "
"union[int,constrained-str,function-plain[str()]]]",
],
"msg": "Input should be a valid decimal",
"input": "six",
},

View File

@@ -277,13 +277,15 @@ def test_response_model_no_annotation_return_exact_dict():
def test_response_model_no_annotation_return_invalid_dict():
with pytest.raises(ResponseValidationError):
with pytest.raises(ResponseValidationError) as excinfo:
client.get("/response_model-no_annotation-return_invalid_dict")
assert "missing" in str(excinfo.value)
def test_response_model_no_annotation_return_invalid_model():
with pytest.raises(ResponseValidationError):
with pytest.raises(ResponseValidationError) as excinfo:
client.get("/response_model-no_annotation-return_invalid_model")
assert "missing" in str(excinfo.value)
def test_response_model_no_annotation_return_dict_with_extra_data():
@@ -313,13 +315,15 @@ def test_no_response_model_annotation_return_exact_dict():
def test_no_response_model_annotation_return_invalid_dict():
with pytest.raises(ResponseValidationError):
with pytest.raises(ResponseValidationError) as excinfo:
client.get("/no_response_model-annotation-return_invalid_dict")
assert "missing" in str(excinfo.value)
def test_no_response_model_annotation_return_invalid_model():
with pytest.raises(ResponseValidationError):
with pytest.raises(ResponseValidationError) as excinfo:
client.get("/no_response_model-annotation-return_invalid_model")
assert "missing" in str(excinfo.value)
def test_no_response_model_annotation_return_dict_with_extra_data():
@@ -395,13 +399,15 @@ def test_response_model_model1_annotation_model2_return_exact_dict():
def test_response_model_model1_annotation_model2_return_invalid_dict():
with pytest.raises(ResponseValidationError):
with pytest.raises(ResponseValidationError) as excinfo:
client.get("/response_model_model1-annotation_model2-return_invalid_dict")
assert "missing" in str(excinfo.value)
def test_response_model_model1_annotation_model2_return_invalid_model():
with pytest.raises(ResponseValidationError):
with pytest.raises(ResponseValidationError) as excinfo:
client.get("/response_model_model1-annotation_model2-return_invalid_model")
assert "missing" in str(excinfo.value)
def test_response_model_model1_annotation_model2_return_dict_with_extra_data():

View File

@@ -1,7 +1,8 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
def get_client():
@@ -36,7 +37,181 @@ def test_put(client: TestClient):
}
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
},
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"ItemInput": {
"title": "Item",
"type": "object",
"properties": {
"name": {
"title": "Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ItemOutput": {
"title": "Item",
"type": "object",
"required": ["name", "description", "price", "tax", "tags"],
"properties": {
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {
"anyOf": [{"type": "number"}, {"type": "null"}],
"title": "Price",
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -124,36 +299,9 @@ def test_openapi_schema(client: TestClient):
"title": "Item",
"type": "object",
"properties": {
"name": IsDict(
{
"title": "Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Name", "type": "string"}
),
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"price": IsDict(
{
"title": "Price",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Price", "type": "number"}
),
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",

View File

@@ -1,8 +1,7 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
@@ -41,7 +40,182 @@ def test_put(client: TestClient):
@needs_py310
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
},
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"ItemInput": {
"title": "Item",
"type": "object",
"properties": {
"name": {
"title": "Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ItemOutput": {
"title": "Item",
"type": "object",
"required": ["name", "description", "price", "tax", "tags"],
"properties": {
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {
"anyOf": [{"type": "number"}, {"type": "null"}],
"title": "Price",
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_py310
@needs_pydanticv1
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -129,36 +303,9 @@ def test_openapi_schema(client: TestClient):
"title": "Item",
"type": "object",
"properties": {
"name": IsDict(
{
"title": "Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Name", "type": "string"}
),
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"price": IsDict(
{
"title": "Price",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Price", "type": "number"}
),
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",

View File

@@ -1,8 +1,7 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39
from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
@@ -41,7 +40,182 @@ def test_put(client: TestClient):
@needs_py39
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
},
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"ItemInput": {
"title": "Item",
"type": "object",
"properties": {
"name": {
"title": "Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {
"title": "Price",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ItemOutput": {
"title": "Item",
"type": "object",
"required": ["name", "description", "price", "tax", "tags"],
"properties": {
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {
"anyOf": [{"type": "number"}, {"type": "null"}],
"title": "Price",
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_py39
@needs_pydanticv1
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -129,36 +303,9 @@ def test_openapi_schema(client: TestClient):
"title": "Item",
"type": "object",
"properties": {
"name": IsDict(
{
"title": "Name",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Name", "type": "string"}
),
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"price": IsDict(
{
"title": "Price",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Price", "type": "number"}
),
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",

View File

@@ -1,4 +1,4 @@
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.dataclasses.tutorial002 import app
@@ -21,8 +21,7 @@ def test_get_item():
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
data = response.json()
assert data == {
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
@@ -47,7 +46,11 @@ def test_openapi_schema():
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "price", "tags", "description", "tax"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
@@ -57,7 +60,6 @@ def test_openapi_schema():
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
}
)
| IsDict(

View File

@@ -1,8 +1,9 @@
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dataclasses.tutorial003 import app
from ...utils import needs_pydanticv1, needs_pydanticv2
client = TestClient(app)
@@ -52,7 +53,157 @@ def test_get_authors():
]
@needs_pydanticv2
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/authors/{author_id}/items/": {
"post": {
"summary": "Create Author Items",
"operationId": "create_author_items_authors__author_id__items__post",
"parameters": [
{
"required": True,
"schema": {"title": "Author Id", "type": "string"},
"name": "author_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"title": "Items",
"type": "array",
"items": {"$ref": "#/components/schemas/ItemInput"},
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Author"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/authors/": {
"get": {
"summary": "Get Authors",
"operationId": "get_authors_authors__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response Get Authors Authors Get",
"type": "array",
"items": {
"$ref": "#/components/schemas/Author"
},
}
}
},
}
},
}
},
},
"components": {
"schemas": {
"Author": {
"title": "Author",
"required": ["name", "items"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"items": {
"title": "Items",
"type": "array",
"items": {"$ref": "#/components/schemas/ItemOutput"},
},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
"ItemInput": {
"title": "Item",
"required": ["name"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
},
},
"ItemOutput": {
"title": "Item",
"required": ["name", "description"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_openapi_schema_pv1():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
@@ -136,22 +287,11 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"items": IsDict(
{
"title": "Items",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
"default": [],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Items",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
}
),
"items": {
"title": "Items",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
},
},
},
"HTTPValidationError": {
@@ -171,16 +311,7 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"description": {"title": "Description", "type": "string"},
},
},
"ValidationError": {

View File

@@ -1,3 +1,4 @@
from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
from docs_src.extra_models.tutorial003 import app
@@ -76,7 +77,11 @@ def test_openapi_schema():
"schemas": {
"PlaneItem": {
"title": "PlaneItem",
"required": ["description", "size"],
"required": IsOneOf(
["description", "type", "size"],
# TODO: remove when deprecating Pydantic v1
["description", "size"],
),
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},
@@ -86,7 +91,11 @@ def test_openapi_schema():
},
"CarItem": {
"title": "CarItem",
"required": ["description"],
"required": IsOneOf(
["description", "type"],
# TODO: remove when deprecating Pydantic v1
["description"],
),
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},

View File

@@ -1,4 +1,5 @@
import pytest
from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -86,7 +87,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"PlaneItem": {
"title": "PlaneItem",
"required": ["description", "size"],
"required": IsOneOf(
["description", "type", "size"],
# TODO: remove when deprecating Pydantic v1
["description", "size"],
),
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},
@@ -96,7 +101,11 @@ def test_openapi_schema(client: TestClient):
},
"CarItem": {
"title": "CarItem",
"required": ["description"],
"required": IsOneOf(
["description", "type"],
# TODO: remove when deprecating Pydantic v1
["description"],
),
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},

View File

@@ -1,8 +1,9 @@
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.path_operation_advanced_configuration.tutorial004 import app
from ...utils import needs_pydanticv1, needs_pydanticv2
client = TestClient(app)
@@ -18,7 +19,137 @@ def test_query_params_str_validations():
}
@needs_pydanticv2
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create an item",
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"ItemInput": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ItemOutput": {
"title": "Item",
"required": ["name", "description", "price", "tax", "tags"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_openapi_schema_pv1():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -69,27 +200,9 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
"tax": {"title": "Tax", "type": "number"},
"tags": {
"title": "Tags",
"uniqueItems": True,

View File

@@ -1,8 +1,9 @@
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.path_operation_configuration.tutorial005 import app
from ...utils import needs_pydanticv1, needs_pydanticv2
client = TestClient(app)
@@ -18,7 +19,137 @@ def test_query_params_str_validations():
}
@needs_pydanticv2
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "The created item",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create an item",
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"ItemInput": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ItemOutput": {
"title": "Item",
"required": ["name", "description", "price", "tax", "tags"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_openapi_schema_pv1():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -69,27 +200,9 @@ def test_openapi_schema():
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
"tax": {"title": "Tax", "type": "number"},
"tags": {
"title": "Tags",
"uniqueItems": True,

View File

@@ -1,8 +1,7 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
@@ -27,7 +26,138 @@ def test_query_params_str_validations(client: TestClient):
@needs_py310
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "The created item",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create an item",
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"ItemInput": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ItemOutput": {
"title": "Item",
"required": ["name", "description", "price", "tax", "tags"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_py310
@needs_pydanticv1
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -78,27 +208,9 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
"tax": {"title": "Tax", "type": "number"},
"tags": {
"title": "Tags",
"uniqueItems": True,

View File

@@ -1,8 +1,7 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39
from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
@@ -27,7 +26,138 @@ def test_query_params_str_validations(client: TestClient):
@needs_py39
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "The created item",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ItemOutput"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create an item",
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"ItemInput": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ItemOutput": {
"title": "Item",
"required": ["name", "description", "price", "tax", "tags"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {"title": "Price", "type": "number"},
"tax": {
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
},
"tags": {
"title": "Tags",
"uniqueItems": True,
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_py39
@needs_pydanticv1
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@@ -78,27 +208,9 @@ def test_openapi_schema(client: TestClient):
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
"tax": {"title": "Tax", "type": "number"},
"tags": {
"title": "Tags",
"uniqueItems": True,

View File

@@ -1,4 +1,4 @@
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.response_model.tutorial003 import app
@@ -70,7 +70,11 @@ def test_openapi_schema():
"schemas": {
"UserOut": {
"title": "UserOut",
"required": ["username", "email"],
"required": IsOneOf(
["username", "email", "full_name"],
# TODO: remove when deprecating Pydantic v1
["username", "email"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,4 +1,4 @@
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.response_model.tutorial003_01 import app
@@ -70,7 +70,11 @@ def test_openapi_schema():
"schemas": {
"BaseUser": {
"title": "BaseUser",
"required": ["username", "email"],
"required": IsOneOf(
["username", "email", "full_name"],
# TODO: remove when deprecating Pydantic v1
["username", "email"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -79,7 +79,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"BaseUser": {
"title": "BaseUser",
"required": ["username", "email"],
"required": IsOneOf(
["username", "email", "full_name"],
# TODO: remove when deprecating Pydantic v1
["username", "email"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -79,7 +79,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"UserOut": {
"title": "UserOut",
"required": ["username", "email"],
"required": IsOneOf(
["username", "email", "full_name"],
# TODO: remove when deprecating Pydantic v1
["username", "email"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.response_model.tutorial004 import app
@@ -79,7 +79,11 @@ def test_openapi_schema():
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "description", "price", "tax", "tags"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -87,7 +87,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "description", "price", "tax", "tags"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py39
@@ -87,7 +87,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "description", "price", "tax", "tags"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -1,4 +1,4 @@
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.response_model.tutorial005 import app
@@ -102,7 +102,11 @@ def test_openapi_schema():
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "description", "price", "tax"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -112,7 +112,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "description", "price", "tax"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -1,4 +1,4 @@
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.response_model.tutorial006 import app
@@ -102,7 +102,11 @@ def test_openapi_schema():
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "description", "price", "tax"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -112,7 +112,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"required": IsOneOf(
["name", "description", "price", "tax"],
# TODO: remove when deprecating Pydantic v1
["name", "price"],
),
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},

View File

@@ -1,4 +1,4 @@
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.security.tutorial005 import (
@@ -267,7 +267,11 @@ def test_openapi_schema():
"schemas": {
"User": {
"title": "User",
"required": ["username"],
"required": IsOneOf(
["username", "email", "full_name", "disabled"],
# TODO: remove when deprecating Pydantic v1
["username"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,4 +1,4 @@
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from docs_src.security.tutorial005_an import (
@@ -267,7 +267,11 @@ def test_openapi_schema():
"schemas": {
"User": {
"title": "User",
"required": ["username"],
"required": IsOneOf(
["username", "email", "full_name", "disabled"],
# TODO: remove when deprecating Pydantic v1
["username"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"User": {
"title": "User",
"required": ["username"],
"required": IsOneOf(
["username", "email", "full_name", "disabled"],
# TODO: remove when deprecating Pydantic v1
["username"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py39
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"User": {
"title": "User",
"required": ["username"],
"required": IsOneOf(
["username", "email", "full_name", "disabled"],
# TODO: remove when deprecating Pydantic v1
["username"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"User": {
"title": "User",
"required": ["username"],
"required": IsOneOf(
["username", "email", "full_name", "disabled"],
# TODO: remove when deprecating Pydantic v1
["username"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,5 @@
import pytest
from dirty_equals import IsDict
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py39
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
"schemas": {
"User": {
"title": "User",
"required": ["username"],
"required": IsOneOf(
["username", "email", "full_name", "disabled"],
# TODO: remove when deprecating Pydantic v1
["username"],
),
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"},