diff --git a/.github/actions/notify-translations/Dockerfile b/.github/actions/notify-translations/Dockerfile
deleted file mode 100644
index b68b4bb1a..000000000
--- a/.github/actions/notify-translations/Dockerfile
+++ /dev/null
@@ -1,7 +0,0 @@
-FROM python:3.9
-
-RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0"
-
-COPY ./app /app
-
-CMD ["python", "/app/main.py"]
diff --git a/.github/actions/notify-translations/action.yml b/.github/actions/notify-translations/action.yml
deleted file mode 100644
index c3579977c..000000000
--- a/.github/actions/notify-translations/action.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-name: "Notify Translations"
-description: "Notify in the issue for a translation when there's a new PR available"
-author: "Sebastiรกn Ramรญrez "
-inputs:
- token:
- description: 'Token, to read the GitHub API. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
- required: true
-runs:
- using: 'docker'
- image: 'Dockerfile'
diff --git a/.github/actions/people/Dockerfile b/.github/actions/people/Dockerfile
deleted file mode 100644
index 1455106bd..000000000
--- a/.github/actions/people/Dockerfile
+++ /dev/null
@@ -1,7 +0,0 @@
-FROM python:3.9
-
-RUN pip install httpx PyGithub "pydantic==2.0.2" pydantic-settings "pyyaml>=5.3.1,<6.0.0"
-
-COPY ./app /app
-
-CMD ["python", "/app/main.py"]
diff --git a/.github/actions/people/action.yml b/.github/actions/people/action.yml
deleted file mode 100644
index 71745b874..000000000
--- a/.github/actions/people/action.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-name: "Generate FastAPI People"
-description: "Generate the data for the FastAPI People page"
-author: "Sebastiรกn Ramรญrez "
-inputs:
- token:
- description: 'User token, to read the GitHub API. Can be passed in using {{ secrets.FASTAPI_PEOPLE }}'
- required: true
-runs:
- using: 'docker'
- image: 'Dockerfile'
diff --git a/.github/actions/people/app/main.py b/.github/actions/people/app/main.py
deleted file mode 100644
index b752d9d2b..000000000
--- a/.github/actions/people/app/main.py
+++ /dev/null
@@ -1,682 +0,0 @@
-import logging
-import subprocess
-import sys
-from collections import Counter, defaultdict
-from datetime import datetime, timedelta, timezone
-from pathlib import Path
-from typing import Any, Container, DefaultDict, Dict, List, Set, Union
-
-import httpx
-import yaml
-from github import Github
-from pydantic import BaseModel, SecretStr
-from pydantic_settings import BaseSettings
-
-github_graphql_url = "https://api.github.com/graphql"
-questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0"
-
-discussions_query = """
-query Q($after: String, $category_id: ID) {
- repository(name: "fastapi", owner: "fastapi") {
- discussions(first: 100, after: $after, categoryId: $category_id) {
- edges {
- cursor
- node {
- number
- author {
- login
- avatarUrl
- url
- }
- title
- createdAt
- comments(first: 100) {
- nodes {
- createdAt
- author {
- login
- avatarUrl
- url
- }
- isAnswer
- replies(first: 10) {
- nodes {
- createdAt
- author {
- login
- avatarUrl
- url
- }
- }
- }
- }
- }
- }
- }
- }
- }
-}
-"""
-
-
-prs_query = """
-query Q($after: String) {
- repository(name: "fastapi", owner: "fastapi") {
- pullRequests(first: 100, after: $after) {
- edges {
- cursor
- node {
- number
- labels(first: 100) {
- nodes {
- name
- }
- }
- author {
- login
- avatarUrl
- url
- }
- title
- createdAt
- state
- comments(first: 100) {
- nodes {
- createdAt
- author {
- login
- avatarUrl
- url
- }
- }
- }
- reviews(first:100) {
- nodes {
- author {
- login
- avatarUrl
- url
- }
- state
- }
- }
- }
- }
- }
- }
-}
-"""
-
-sponsors_query = """
-query Q($after: String) {
- user(login: "fastapi") {
- sponsorshipsAsMaintainer(first: 100, after: $after) {
- edges {
- cursor
- node {
- sponsorEntity {
- ... on Organization {
- login
- avatarUrl
- url
- }
- ... on User {
- login
- avatarUrl
- url
- }
- }
- tier {
- name
- monthlyPriceInDollars
- }
- }
- }
- }
- }
-}
-"""
-
-
-class Author(BaseModel):
- login: str
- avatarUrl: str
- url: str
-
-
-# Discussions
-
-
-class CommentsNode(BaseModel):
- createdAt: datetime
- author: Union[Author, None] = None
-
-
-class Replies(BaseModel):
- nodes: List[CommentsNode]
-
-
-class DiscussionsCommentsNode(CommentsNode):
- replies: Replies
-
-
-class Comments(BaseModel):
- nodes: List[CommentsNode]
-
-
-class DiscussionsComments(BaseModel):
- nodes: List[DiscussionsCommentsNode]
-
-
-class DiscussionsNode(BaseModel):
- number: int
- author: Union[Author, None] = None
- title: str
- createdAt: datetime
- comments: DiscussionsComments
-
-
-class DiscussionsEdge(BaseModel):
- cursor: str
- node: DiscussionsNode
-
-
-class Discussions(BaseModel):
- edges: List[DiscussionsEdge]
-
-
-class DiscussionsRepository(BaseModel):
- discussions: Discussions
-
-
-class DiscussionsResponseData(BaseModel):
- repository: DiscussionsRepository
-
-
-class DiscussionsResponse(BaseModel):
- data: DiscussionsResponseData
-
-
-# PRs
-
-
-class LabelNode(BaseModel):
- name: str
-
-
-class Labels(BaseModel):
- nodes: List[LabelNode]
-
-
-class ReviewNode(BaseModel):
- author: Union[Author, None] = None
- state: str
-
-
-class Reviews(BaseModel):
- nodes: List[ReviewNode]
-
-
-class PullRequestNode(BaseModel):
- number: int
- labels: Labels
- author: Union[Author, None] = None
- title: str
- createdAt: datetime
- state: str
- comments: Comments
- reviews: Reviews
-
-
-class PullRequestEdge(BaseModel):
- cursor: str
- node: PullRequestNode
-
-
-class PullRequests(BaseModel):
- edges: List[PullRequestEdge]
-
-
-class PRsRepository(BaseModel):
- pullRequests: PullRequests
-
-
-class PRsResponseData(BaseModel):
- repository: PRsRepository
-
-
-class PRsResponse(BaseModel):
- data: PRsResponseData
-
-
-# Sponsors
-
-
-class SponsorEntity(BaseModel):
- login: str
- avatarUrl: str
- url: str
-
-
-class Tier(BaseModel):
- name: str
- monthlyPriceInDollars: float
-
-
-class SponsorshipAsMaintainerNode(BaseModel):
- sponsorEntity: SponsorEntity
- tier: Tier
-
-
-class SponsorshipAsMaintainerEdge(BaseModel):
- cursor: str
- node: SponsorshipAsMaintainerNode
-
-
-class SponsorshipAsMaintainer(BaseModel):
- edges: List[SponsorshipAsMaintainerEdge]
-
-
-class SponsorsUser(BaseModel):
- sponsorshipsAsMaintainer: SponsorshipAsMaintainer
-
-
-class SponsorsResponseData(BaseModel):
- user: SponsorsUser
-
-
-class SponsorsResponse(BaseModel):
- data: SponsorsResponseData
-
-
-class Settings(BaseSettings):
- input_token: SecretStr
- github_repository: str
- httpx_timeout: int = 30
-
-
-def get_graphql_response(
- *,
- settings: Settings,
- query: str,
- after: Union[str, None] = None,
- category_id: Union[str, None] = None,
-) -> Dict[str, Any]:
- headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
- # category_id is only used by one query, but GraphQL allows unused variables, so
- # keep it here for simplicity
- variables = {"after": after, "category_id": category_id}
- response = httpx.post(
- github_graphql_url,
- headers=headers,
- timeout=settings.httpx_timeout,
- json={"query": query, "variables": variables, "operationName": "Q"},
- )
- if response.status_code != 200:
- logging.error(
- f"Response was not 200, after: {after}, category_id: {category_id}"
- )
- logging.error(response.text)
- raise RuntimeError(response.text)
- 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
-
-
-def get_graphql_question_discussion_edges(
- *,
- settings: Settings,
- after: Union[str, None] = None,
-):
- data = get_graphql_response(
- settings=settings,
- query=discussions_query,
- after=after,
- category_id=questions_category_id,
- )
- 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.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.model_validate(data)
- return graphql_response.data.user.sponsorshipsAsMaintainer.edges
-
-
-class DiscussionExpertsResults(BaseModel):
- commenters: Counter
- last_month_commenters: Counter
- three_months_commenters: Counter
- six_months_commenters: Counter
- one_year_commenters: Counter
- authors: Dict[str, Author]
-
-
-def get_discussion_nodes(settings: Settings) -> List[DiscussionsNode]:
- discussion_nodes: List[DiscussionsNode] = []
- discussion_edges = get_graphql_question_discussion_edges(settings=settings)
-
- while discussion_edges:
- for discussion_edge in discussion_edges:
- discussion_nodes.append(discussion_edge.node)
- last_edge = discussion_edges[-1]
- discussion_edges = get_graphql_question_discussion_edges(
- settings=settings, after=last_edge.cursor
- )
- return discussion_nodes
-
-
-def get_discussions_experts(
- discussion_nodes: List[DiscussionsNode],
-) -> DiscussionExpertsResults:
- commenters = Counter()
- last_month_commenters = Counter()
- three_months_commenters = Counter()
- six_months_commenters = Counter()
- one_year_commenters = Counter()
- authors: Dict[str, Author] = {}
-
- now = datetime.now(tz=timezone.utc)
- one_month_ago = now - timedelta(days=30)
- three_months_ago = now - timedelta(days=90)
- six_months_ago = now - timedelta(days=180)
- one_year_ago = now - timedelta(days=365)
-
- for discussion in discussion_nodes:
- discussion_author_name = None
- if discussion.author:
- authors[discussion.author.login] = discussion.author
- discussion_author_name = discussion.author.login
- discussion_commentors: dict[str, datetime] = {}
- for comment in discussion.comments.nodes:
- if comment.author:
- authors[comment.author.login] = comment.author
- if comment.author.login != discussion_author_name:
- author_time = discussion_commentors.get(
- comment.author.login, comment.createdAt
- )
- discussion_commentors[comment.author.login] = max(
- author_time, comment.createdAt
- )
- for reply in comment.replies.nodes:
- if reply.author:
- authors[reply.author.login] = reply.author
- if reply.author.login != discussion_author_name:
- author_time = discussion_commentors.get(
- reply.author.login, reply.createdAt
- )
- discussion_commentors[reply.author.login] = max(
- author_time, reply.createdAt
- )
- for author_name, author_time in discussion_commentors.items():
- commenters[author_name] += 1
- if author_time > one_month_ago:
- last_month_commenters[author_name] += 1
- if author_time > three_months_ago:
- three_months_commenters[author_name] += 1
- if author_time > six_months_ago:
- six_months_commenters[author_name] += 1
- if author_time > one_year_ago:
- one_year_commenters[author_name] += 1
- discussion_experts_results = DiscussionExpertsResults(
- authors=authors,
- commenters=commenters,
- last_month_commenters=last_month_commenters,
- three_months_commenters=three_months_commenters,
- six_months_commenters=six_months_commenters,
- one_year_commenters=one_year_commenters,
- )
- return discussion_experts_results
-
-
-def get_pr_nodes(settings: Settings) -> List[PullRequestNode]:
- pr_nodes: List[PullRequestNode] = []
- pr_edges = get_graphql_pr_edges(settings=settings)
-
- while pr_edges:
- for edge in pr_edges:
- pr_nodes.append(edge.node)
- last_edge = pr_edges[-1]
- pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor)
- return pr_nodes
-
-
-class ContributorsResults(BaseModel):
- contributors: Counter
- commenters: Counter
- reviewers: Counter
- translation_reviewers: Counter
- authors: Dict[str, Author]
-
-
-def get_contributors(pr_nodes: List[PullRequestNode]) -> ContributorsResults:
- contributors = Counter()
- commenters = Counter()
- reviewers = Counter()
- translation_reviewers = Counter()
- authors: Dict[str, Author] = {}
-
- for pr in pr_nodes:
- author_name = None
- if pr.author:
- authors[pr.author.login] = pr.author
- author_name = pr.author.login
- pr_commentors: Set[str] = set()
- pr_reviewers: Set[str] = set()
- for comment in pr.comments.nodes:
- if comment.author:
- authors[comment.author.login] = comment.author
- if comment.author.login == author_name:
- continue
- pr_commentors.add(comment.author.login)
- for author_name in pr_commentors:
- commenters[author_name] += 1
- for review in pr.reviews.nodes:
- if review.author:
- authors[review.author.login] = review.author
- pr_reviewers.add(review.author.login)
- for label in pr.labels.nodes:
- if label.name == "lang-all":
- translation_reviewers[review.author.login] += 1
- break
- for reviewer in pr_reviewers:
- reviewers[reviewer] += 1
- if pr.state == "MERGED" and pr.author:
- contributors[pr.author.login] += 1
- return ContributorsResults(
- contributors=contributors,
- commenters=commenters,
- reviewers=reviewers,
- translation_reviewers=translation_reviewers,
- authors=authors,
- )
-
-
-def get_individual_sponsors(settings: Settings):
- nodes: List[SponsorshipAsMaintainerNode] = []
- edges = get_graphql_sponsor_edges(settings=settings)
-
- while edges:
- for edge in edges:
- nodes.append(edge.node)
- last_edge = edges[-1]
- edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor)
-
- tiers: DefaultDict[float, Dict[str, SponsorEntity]] = defaultdict(dict)
- for node in nodes:
- tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = (
- node.sponsorEntity
- )
- return tiers
-
-
-def get_top_users(
- *,
- counter: Counter,
- authors: Dict[str, Author],
- skip_users: Container[str],
- min_count: int = 2,
-):
- users = []
- for commenter, count in counter.most_common(50):
- if commenter in skip_users:
- continue
- if count >= min_count:
- author = authors[commenter]
- users.append(
- {
- "login": commenter,
- "count": count,
- "avatarUrl": author.avatarUrl,
- "url": author.url,
- }
- )
- return users
-
-
-if __name__ == "__main__":
- logging.basicConfig(level=logging.INFO)
- settings = Settings()
- logging.info(f"Using config: {settings.model_dump_json()}")
- g = Github(settings.input_token.get_secret_value())
- repo = g.get_repo(settings.github_repository)
- discussion_nodes = get_discussion_nodes(settings=settings)
- experts_results = get_discussions_experts(discussion_nodes=discussion_nodes)
- pr_nodes = get_pr_nodes(settings=settings)
- contributors_results = get_contributors(pr_nodes=pr_nodes)
- authors = {**experts_results.authors, **contributors_results.authors}
- maintainers_logins = {"tiangolo"}
- bot_names = {"codecov", "github-actions", "pre-commit-ci", "dependabot"}
- maintainers = []
- for login in maintainers_logins:
- user = authors[login]
- maintainers.append(
- {
- "login": login,
- "answers": experts_results.commenters[login],
- "prs": contributors_results.contributors[login],
- "avatarUrl": user.avatarUrl,
- "url": user.url,
- }
- )
-
- skip_users = maintainers_logins | bot_names
- experts = get_top_users(
- counter=experts_results.commenters,
- authors=authors,
- skip_users=skip_users,
- )
- last_month_experts = get_top_users(
- counter=experts_results.last_month_commenters,
- authors=authors,
- skip_users=skip_users,
- )
- three_months_experts = get_top_users(
- counter=experts_results.three_months_commenters,
- authors=authors,
- skip_users=skip_users,
- )
- six_months_experts = get_top_users(
- counter=experts_results.six_months_commenters,
- authors=authors,
- skip_users=skip_users,
- )
- one_year_experts = get_top_users(
- counter=experts_results.one_year_commenters,
- authors=authors,
- skip_users=skip_users,
- )
- top_contributors = get_top_users(
- counter=contributors_results.contributors,
- authors=authors,
- skip_users=skip_users,
- )
- top_reviewers = get_top_users(
- counter=contributors_results.reviewers,
- authors=authors,
- skip_users=skip_users,
- )
- top_translations_reviewers = get_top_users(
- counter=contributors_results.translation_reviewers,
- authors=authors,
- skip_users=skip_users,
- )
-
- tiers = get_individual_sponsors(settings=settings)
- keys = list(tiers.keys())
- keys.sort(reverse=True)
- sponsors = []
- for key in keys:
- sponsor_group = []
- for login, sponsor in tiers[key].items():
- sponsor_group.append(
- {"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url}
- )
- sponsors.append(sponsor_group)
-
- people = {
- "maintainers": maintainers,
- "experts": experts,
- "last_month_experts": last_month_experts,
- "three_months_experts": three_months_experts,
- "six_months_experts": six_months_experts,
- "one_year_experts": one_year_experts,
- "top_contributors": top_contributors,
- "top_reviewers": top_reviewers,
- "top_translations_reviewers": top_translations_reviewers,
- }
- github_sponsors = {
- "sponsors": sponsors,
- }
- # For local development
- # people_path = Path("../../../../docs/en/data/people.yml")
- people_path = Path("./docs/en/data/people.yml")
- github_sponsors_path = Path("./docs/en/data/github_sponsors.yml")
- people_old_content = people_path.read_text(encoding="utf-8")
- github_sponsors_old_content = github_sponsors_path.read_text(encoding="utf-8")
- new_people_content = yaml.dump(
- people, sort_keys=False, width=200, allow_unicode=True
- )
- new_github_sponsors_content = yaml.dump(
- github_sponsors, sort_keys=False, width=200, allow_unicode=True
- )
- if (
- people_old_content == new_people_content
- and github_sponsors_old_content == new_github_sponsors_content
- ):
- logging.info("The FastAPI People data hasn't changed, finishing.")
- sys.exit(0)
- people_path.write_text(new_people_content, encoding="utf-8")
- github_sponsors_path.write_text(new_github_sponsors_content, encoding="utf-8")
- logging.info("Setting up GitHub Actions git user")
- subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
- subprocess.run(
- ["git", "config", "user.email", "github-actions@github.com"], check=True
- )
- branch_name = "fastapi-people"
- logging.info(f"Creating a new branch {branch_name}")
- subprocess.run(["git", "checkout", "-b", branch_name], check=True)
- logging.info("Adding updated file")
- subprocess.run(
- ["git", "add", str(people_path), str(github_sponsors_path)], check=True
- )
- logging.info("Committing updated file")
- message = "๐ฅ Update FastAPI People"
- result = subprocess.run(["git", "commit", "-m", message], check=True)
- logging.info("Pushing branch")
- subprocess.run(["git", "push", "origin", branch_name], check=True)
- logging.info("Creating PR")
- pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
- logging.info(f"Created PR: {pr.number}")
- logging.info("Finished")
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index d9ed61910..0b3096143 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -64,7 +64,7 @@ jobs:
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 ) }}
# TODO: Use v3 when it's fixed, probably in v3.11
# https://github.com/cloudflare/wrangler-action/issues/307
- uses: cloudflare/wrangler-action@v3.13
+ uses: cloudflare/wrangler-action@v3.14
# uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
diff --git a/.github/workflows/notify-translations.yml b/.github/workflows/notify-translations.yml
index 187322bca..c96992689 100644
--- a/.github/workflows/notify-translations.yml
+++ b/.github/workflows/notify-translations.yml
@@ -15,15 +15,14 @@ on:
required: false
default: 'false'
-permissions:
- discussions: write
-
env:
UV_SYSTEM_PYTHON: 1
jobs:
- notify-translations:
+ job:
runs-on: ubuntu-latest
+ permissions:
+ discussions: write
steps:
- name: Dump GitHub context
env:
@@ -42,12 +41,19 @@ jobs:
cache-dependency-glob: |
requirements**.txt
pyproject.toml
+ - name: Install Dependencies
+ run: uv pip install -r requirements-github-actions.txt
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- - uses: ./.github/actions/notify-translations
- with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Notify Translations
+ run: python ./scripts/notify_translations.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NUMBER: ${{ github.event.inputs.number || null }}
+ DEBUG: ${{ github.event.inputs.debug_enabled || 'false' }}
diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml
index c60c63d1b..6ec3c1ad2 100644
--- a/.github/workflows/people.yml
+++ b/.github/workflows/people.yml
@@ -6,29 +6,48 @@ on:
workflow_dispatch:
inputs:
debug_enabled:
- description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
+ description: Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)
required: false
- default: 'false'
+ default: "false"
+
+env:
+ UV_SYSTEM_PYTHON: 1
jobs:
- fastapi-people:
+ job:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
+ permissions:
+ contents: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
- # Ref: https://github.com/actions/runner/issues/2033
- - name: Fix git safe.directory in container
- run: mkdir -p /home/runner/work/_temp/_github_home && printf "[safe]\n\tdirectory = /github/workspace" > /home/runner/work/_temp/_github_home/.gitconfig
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+ - name: Setup uv
+ uses: astral-sh/setup-uv@v5
+ with:
+ version: "0.4.15"
+ enable-cache: true
+ cache-dependency-glob: |
+ requirements**.txt
+ pyproject.toml
+ - name: Install Dependencies
+ run: uv pip install -r requirements-github-actions.txt
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- - uses: ./.github/actions/people
- with:
- token: ${{ secrets.FASTAPI_PEOPLE }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}
+ - name: FastAPI People Experts
+ run: python ./scripts/people.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 38df75928..bf88d59b1 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -35,7 +35,7 @@ jobs:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
run: python -m build
- name: Publish
- uses: pypa/gh-action-pypi-publish@v1.12.3
+ uses: pypa/gh-action-pypi-publish@v1.12.4
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e0daf7472..5e8092641 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -48,6 +48,7 @@ jobs:
strategy:
matrix:
python-version:
+ - "3.13"
- "3.12"
- "3.11"
- "3.10"
@@ -81,6 +82,10 @@ jobs:
- name: Install Pydantic v2
if: matrix.pydantic-version == 'pydantic-v2'
run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0"
+ # TODO: Remove this once Python 3.8 is no longer supported
+ - name: Install older AnyIO in Python 3.8
+ if: matrix.python-version == '3.8'
+ run: uv pip install "anyio[trio]<4.0.0"
- run: mkdir coverage
- name: Test
run: bash scripts/test.sh
diff --git a/README.md b/README.md
index 6492ad745..d5d5ced52 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
+
{% for user in (contributors.values() | list)[:50] %}
{% if user.login not in skip_users %}
diff --git a/docs/en/docs/img/sponsors/lambdatest.png b/docs/en/docs/img/sponsors/lambdatest.png
new file mode 100644
index 000000000..674cbcb89
Binary files /dev/null and b/docs/en/docs/img/sponsors/lambdatest.png differ
diff --git a/docs/en/docs/img/sponsors/permit.png b/docs/en/docs/img/sponsors/permit.png
new file mode 100644
index 000000000..4f07f22e2
Binary files /dev/null and b/docs/en/docs/img/sponsors/permit.png differ
diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md
index cbe71c87d..4a2777f25 100644
--- a/docs/en/docs/index.md
+++ b/docs/en/docs/index.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index a242407ea..5f3d98e5c 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -9,6 +9,95 @@ hide:
### Refactors
+* โ
Simplify tests for `query_params_str_validations`. PR [#13218](https://github.com/fastapi/fastapi/pull/13218) by [@alv2017](https://github.com/alv2017).
+* โ
Simplify tests for `app_testing`. PR [#13220](https://github.com/fastapi/fastapi/pull/13220) by [@alv2017](https://github.com/alv2017).
+* โ
Simplify tests for `dependency_testing`. PR [#13223](https://github.com/fastapi/fastapi/pull/13223) by [@alv2017](https://github.com/alv2017).
+
+### Docs
+
+* ๐ Fix issue with Swagger theme change example in the official tutorial. PR [#13289](https://github.com/fastapi/fastapi/pull/13289) by [@Zerohertz](https://github.com/Zerohertz).
+* ๐ Add more precise description of HTTP status code range in docs. PR [#13347](https://github.com/fastapi/fastapi/pull/13347) by [@DanielYang59](https://github.com/DanielYang59).
+* ๐ฅ Remove manual type annotations in JWT tutorial to avoid typing expectations (JWT doesn't provide more types). PR [#13378](https://github.com/fastapi/fastapi/pull/13378) by [@tiangolo](https://github.com/tiangolo).
+* ๐ Update docs for Query Params and String Validations, remove obsolete Ellipsis docs (`...`). PR [#13377](https://github.com/fastapi/fastapi/pull/13377) by [@tiangolo](https://github.com/tiangolo).
+* โ๏ธ Remove duplicate title in docs `body-multiple-params`. PR [#13345](https://github.com/fastapi/fastapi/pull/13345) by [@DanielYang59](https://github.com/DanielYang59).
+* ๐ Fix test badge. PR [#13313](https://github.com/fastapi/fastapi/pull/13313) by [@esadek](https://github.com/esadek).
+
+### Translations
+
+* ๐ Update Korean translation for `docs/ko/docs/help-fastapi.md`. PR [#13262](https://github.com/fastapi/fastapi/pull/13262) by [@Zerohertz](https://github.com/Zerohertz).
+* ๐ Add Korean translation for `docs/ko/docs/advanced/custom-response.md`. PR [#13265](https://github.com/fastapi/fastapi/pull/13265) by [@11kkw](https://github.com/11kkw).
+* ๐ Update Korean translation for `docs/ko/docs/tutorial/security/simple-oauth2.md`. PR [#13335](https://github.com/fastapi/fastapi/pull/13335) by [@yes0ng](https://github.com/yes0ng).
+* ๐ Add Russian translation for `docs/ru/docs/advanced/response-cookies.md`. PR [#13327](https://github.com/fastapi/fastapi/pull/13327) by [@Stepakinoyan](https://github.com/Stepakinoyan).
+* ๐ Add Vietnamese translation for `docs/vi/docs/tutorial/static-files.md`. PR [#11291](https://github.com/fastapi/fastapi/pull/11291) by [@ptt3199](https://github.com/ptt3199).
+* ๐ Add Korean translation for `docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md`. PR [#13257](https://github.com/fastapi/fastapi/pull/13257) by [@11kkw](https://github.com/11kkw).
+* ๐ Add Vietnamese translation for `docs/vi/docs/virtual-environments.md`. PR [#13282](https://github.com/fastapi/fastapi/pull/13282) by [@ptt3199](https://github.com/ptt3199).
+* ๐ Add Ukrainian translation for `docs/uk/docs/tutorial/static-files.md`. PR [#13285](https://github.com/fastapi/fastapi/pull/13285) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
+* ๐ Add Vietnamese translation for `docs/vi/docs/environment-variables.md`. PR [#13287](https://github.com/fastapi/fastapi/pull/13287) by [@ptt3199](https://github.com/ptt3199).
+* ๐ Add Vietnamese translation for `docs/vi/docs/fastapi-cli.md`. PR [#13294](https://github.com/fastapi/fastapi/pull/13294) by [@ptt3199](https://github.com/ptt3199).
+* ๐ Add Ukrainian translation for `docs/uk/docs/features.md`. PR [#13308](https://github.com/fastapi/fastapi/pull/13308) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
+* ๐ Add Ukrainian translation for `docs/uk/docs/learn/index.md`. PR [#13306](https://github.com/fastapi/fastapi/pull/13306) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
+* ๐ Update Portuguese Translation for `docs/pt/docs/deployment/https.md`. PR [#13317](https://github.com/fastapi/fastapi/pull/13317) by [@Joao-Pedro-P-Holanda](https://github.com/Joao-Pedro-P-Holanda).
+* ๐ Update Portuguese Translation for `docs/pt/docs/index.md`. PR [#13328](https://github.com/fastapi/fastapi/pull/13328) by [@ceb10n](https://github.com/ceb10n).
+* ๐ Add Russian translation for `docs/ru/docs/advanced/websockets.md`. PR [#13279](https://github.com/fastapi/fastapi/pull/13279) by [@Rishat-F](https://github.com/Rishat-F).
+
+### Internal
+
+* ๐ง Update sponsors: Add LambdaTest. PR [#13389](https://github.com/fastapi/fastapi/pull/13389) by [@tiangolo](https://github.com/tiangolo).
+* โฌ Bump cloudflare/wrangler-action from 3.13 to 3.14. PR [#13350](https://github.com/fastapi/fastapi/pull/13350) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* โฌ Bump mkdocs-material from 9.5.18 to 9.6.1. PR [#13301](https://github.com/fastapi/fastapi/pull/13301) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* โฌ Bump pillow from 11.0.0 to 11.1.0. PR [#13300](https://github.com/fastapi/fastapi/pull/13300) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ๐ฅ Update FastAPI People - Sponsors. PR [#13295](https://github.com/fastapi/fastapi/pull/13295) by [@tiangolo](https://github.com/tiangolo).
+* ๐ฅ Update FastAPI People - Experts. PR [#13303](https://github.com/fastapi/fastapi/pull/13303) by [@tiangolo](https://github.com/tiangolo).
+* ๐ฅ Update FastAPI GitHub topic repositories. PR [#13302](https://github.com/fastapi/fastapi/pull/13302) by [@tiangolo](https://github.com/tiangolo).
+* ๐ฅ Update FastAPI People - Contributors and Translators. PR [#13293](https://github.com/fastapi/fastapi/pull/13293) by [@tiangolo](https://github.com/tiangolo).
+* โฌ Bump inline-snapshot from 0.18.1 to 0.19.3. PR [#13298](https://github.com/fastapi/fastapi/pull/13298) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ๐ง Update sponsors, add Permit. PR [#13288](https://github.com/fastapi/fastapi/pull/13288) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.115.8
+
+### Fixes
+
+* ๐ Fix `OAuth2PasswordRequestForm` and `OAuth2PasswordRequestFormStrict` fixed `grant_type` "password" RegEx. PR [#9783](https://github.com/fastapi/fastapi/pull/9783) by [@skarfie123](https://github.com/skarfie123).
+
+### Refactors
+
+* โ
Simplify tests for body_multiple_params . PR [#13237](https://github.com/fastapi/fastapi/pull/13237) by [@alejsdev](https://github.com/alejsdev).
+* โป๏ธ Move duplicated code portion to a static method in the `APIKeyBase` super class. PR [#3142](https://github.com/fastapi/fastapi/pull/3142) by [@ShahriyarR](https://github.com/ShahriyarR).
+* โ
Simplify tests for request_files. PR [#13182](https://github.com/fastapi/fastapi/pull/13182) by [@alejsdev](https://github.com/alejsdev).
+
+### Docs
+
+* ๐ Change the word "unwrap" to "unpack" in `docs/en/docs/tutorial/extra-models.md`. PR [#13061](https://github.com/fastapi/fastapi/pull/13061) by [@timothy-jeong](https://github.com/timothy-jeong).
+* ๐ Update Request Body's `tutorial002` to deal with `tax=0` case. PR [#13230](https://github.com/fastapi/fastapi/pull/13230) by [@togogh](https://github.com/togogh).
+* ๐ฅ Update FastAPI People - Experts. PR [#13269](https://github.com/fastapi/fastapi/pull/13269) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* ๐ Add Japanese translation for `docs/ja/docs/environment-variables.md`. PR [#13226](https://github.com/fastapi/fastapi/pull/13226) by [@k94-ishi](https://github.com/k94-ishi).
+* ๐ Add Russian translation for `docs/ru/docs/advanced/async-tests.md`. PR [#13227](https://github.com/fastapi/fastapi/pull/13227) by [@Rishat-F](https://github.com/Rishat-F).
+* ๐ Update Russian translation for `docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#13252](https://github.com/fastapi/fastapi/pull/13252) by [@Rishat-F](https://github.com/Rishat-F).
+* ๐ Add Russian translation for `docs/ru/docs/tutorial/bigger-applications.md`. PR [#13154](https://github.com/fastapi/fastapi/pull/13154) by [@alv2017](https://github.com/alv2017).
+
+### Internal
+
+* โฌ๏ธ Add support for Python 3.13. PR [#13274](https://github.com/fastapi/fastapi/pull/13274) by [@tiangolo](https://github.com/tiangolo).
+* โฌ๏ธ Upgrade AnyIO max version for tests, new range: `>=3.2.1,<5.0.0`. PR [#13273](https://github.com/fastapi/fastapi/pull/13273) by [@tiangolo](https://github.com/tiangolo).
+* ๐ง Update Sponsors badges. PR [#13271](https://github.com/fastapi/fastapi/pull/13271) by [@tiangolo](https://github.com/tiangolo).
+* โป๏ธ Fix `notify_translations.py` empty env var handling for PR label events vs workflow_dispatch. PR [#13272](https://github.com/fastapi/fastapi/pull/13272) by [@tiangolo](https://github.com/tiangolo).
+* โป๏ธ Refactor and move `scripts/notify_translations.py`, no need for a custom GitHub Action. PR [#13270](https://github.com/fastapi/fastapi/pull/13270) by [@tiangolo](https://github.com/tiangolo).
+* ๐จ Update FastAPI People Experts script, refactor and optimize data fetching to handle rate limits. PR [#13267](https://github.com/fastapi/fastapi/pull/13267) by [@tiangolo](https://github.com/tiangolo).
+* โฌ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4. PR [#13251](https://github.com/fastapi/fastapi/pull/13251) by [@dependabot[bot]](https://github.com/apps/dependabot).
+
+## 0.115.7
+
+### Upgrades
+
+* โฌ๏ธ Upgrade `python-multipart` to >=0.0.18. PR [#13219](https://github.com/fastapi/fastapi/pull/13219) by [@DanielKusyDev](https://github.com/DanielKusyDev).
+* โฌ๏ธ Bump Starlette to allow up to 0.45.0: `>=0.40.0,<0.46.0`. PR [#13117](https://github.com/fastapi/fastapi/pull/13117) by [@Kludex](https://github.com/Kludex).
+* โฌ๏ธ Upgrade `jinja2` to >=3.1.5. PR [#13194](https://github.com/fastapi/fastapi/pull/13194) by [@DanielKusyDev](https://github.com/DanielKusyDev).
+
+### Refactors
+
* โ
Simplify tests for websockets. PR [#13202](https://github.com/fastapi/fastapi/pull/13202) by [@alejsdev](https://github.com/alejsdev).
* โ
Simplify tests for request_form_models . PR [#13183](https://github.com/fastapi/fastapi/pull/13183) by [@alejsdev](https://github.com/alejsdev).
* โ
Simplify tests for separate_openapi_schemas. PR [#13201](https://github.com/fastapi/fastapi/pull/13201) by [@alejsdev](https://github.com/alejsdev).
@@ -49,6 +138,7 @@ hide:
### Translations
+* ๐ Update Portuguese Translation for `docs/pt/docs/tutorial/request-forms.md`. PR [#13216](https://github.com/fastapi/fastapi/pull/13216) by [@Joao-Pedro-P-Holanda](https://github.com/Joao-Pedro-P-Holanda).
* ๐ Update Portuguese translation for `docs/pt/docs/advanced/settings.md`. PR [#13209](https://github.com/fastapi/fastapi/pull/13209) by [@ceb10n](https://github.com/ceb10n).
* ๐ Add Portuguese translation for `docs/pt/docs/tutorial/security/oauth2-jwt.md`. PR [#13205](https://github.com/fastapi/fastapi/pull/13205) by [@ceb10n](https://github.com/ceb10n).
* ๐ Add Indonesian translation for `docs/id/docs/index.md`. PR [#13191](https://github.com/fastapi/fastapi/pull/13191) by [@gerry-sabar](https://github.com/gerry-sabar).
@@ -103,6 +193,7 @@ hide:
### Internal
+* ๐ง Add Pydantic 2 trove classifier. PR [#13199](https://github.com/fastapi/fastapi/pull/13199) by [@johnthagen](https://github.com/johnthagen).
* ๐ฅ Update FastAPI People - Sponsors. PR [#13231](https://github.com/fastapi/fastapi/pull/13231) by [@tiangolo](https://github.com/tiangolo).
* ๐ท Refactor FastAPI People Sponsors to use 2 tokens. PR [#13228](https://github.com/fastapi/fastapi/pull/13228) by [@tiangolo](https://github.com/tiangolo).
* ๐ท Update token for FastAPI People - Sponsors. PR [#13225](https://github.com/fastapi/fastapi/pull/13225) by [@tiangolo](https://github.com/tiangolo).
diff --git a/docs/en/docs/tutorial/body-multiple-params.md b/docs/en/docs/tutorial/body-multiple-params.md
index 9fced9652..71b308bb4 100644
--- a/docs/en/docs/tutorial/body-multiple-params.md
+++ b/docs/en/docs/tutorial/body-multiple-params.md
@@ -10,8 +10,6 @@ And you can also declare body parameters as optional, by setting the default to
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
-## Multiple body parameters
-
/// note
Notice that, in this case, the `item` that would be taken from the body is optional. As it has a `None` default value.
diff --git a/docs/en/docs/tutorial/extra-models.md b/docs/en/docs/tutorial/extra-models.md
index 5fac3f69e..ed1590ece 100644
--- a/docs/en/docs/tutorial/extra-models.md
+++ b/docs/en/docs/tutorial/extra-models.md
@@ -70,9 +70,9 @@ we would get a Python `dict` with:
}
```
-#### Unwrapping a `dict`
+#### Unpacking a `dict`
-If we take a `dict` like `user_dict` and pass it to a function (or class) with `**user_dict`, Python will "unwrap" it. It will pass the keys and values of the `user_dict` directly as key-value arguments.
+If we take a `dict` like `user_dict` and pass it to a function (or class) with `**user_dict`, Python will "unpack" it. It will pass the keys and values of the `user_dict` directly as key-value arguments.
So, continuing with the `user_dict` from above, writing:
@@ -117,11 +117,11 @@ would be equivalent to:
UserInDB(**user_in.dict())
```
-...because `user_in.dict()` is a `dict`, and then we make Python "unwrap" it by passing it to `UserInDB` prefixed with `**`.
+...because `user_in.dict()` is a `dict`, and then we make Python "unpack" it by passing it to `UserInDB` prefixed with `**`.
So, we get a Pydantic model from the data in another Pydantic model.
-#### Unwrapping a `dict` and extra keywords
+#### Unpacking a `dict` and extra keywords
And then adding the extra keyword argument `hashed_password=hashed_password`, like in:
diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md
index 1bf16334d..511099186 100644
--- a/docs/en/docs/tutorial/query-params-str-validations.md
+++ b/docs/en/docs/tutorial/query-params-str-validations.md
@@ -6,13 +6,13 @@ Let's take this application as example:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
-The query parameter `q` is of type `Union[str, None]` (or `str | None` in Python 3.10), that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required.
+The query parameter `q` is of type `str | None`, that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required.
/// note
FastAPI will know that the value of `q` is not required because of the default value `= None`.
-The `Union` in `Union[str, None]` will allow your editor to give you better support and detect errors.
+Having `str | None` will allow your editor to give you better support and detect errors.
///
@@ -25,29 +25,9 @@ We are going to enforce that even though `q` is optional, whenever it is provide
To achieve that, first import:
* `Query` from `fastapi`
-* `Annotated` from `typing` (or from `typing_extensions` in Python below 3.9)
+* `Annotated` from `typing`
-//// tab | Python 3.10+
-
-In Python 3.9 or above, `Annotated` is part of the standard library, so you can import it from `typing`.
-
-```Python hl_lines="1 3"
-{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!}
-```
-
-////
-
-//// tab | Python 3.8+
-
-In versions of Python below Python 3.9 you import `Annotated` from `typing_extensions`.
-
-It will already be installed with FastAPI.
-
-```Python hl_lines="3-4"
-{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!}
-```
-
-////
+{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
/// info
@@ -145,54 +125,23 @@ As in this case (without using `Annotated`) we have to replace the default value
So:
-```Python
-q: Union[str, None] = Query(default=None)
-```
-
-...makes the parameter optional, with a default value of `None`, the same as:
-
-```Python
-q: Union[str, None] = None
-```
-
-And in Python 3.10 and above:
-
```Python
q: str | None = Query(default=None)
```
...makes the parameter optional, with a default value of `None`, the same as:
-```Python
-q: str | None = None
-```
-
-But the `Query` versions declare it explicitly as being a query parameter.
-
-/// info
-
-Keep in mind that the most important part to make a parameter optional is the part:
```Python
-= None
-```
-
-or the:
-
-```Python
-= Query(default=None)
+q: str | None = None
```
-as it will use that `None` as the default value, and that way make the parameter **not required**.
-
-The `Union[str, None]` part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required.
-
-///
+But the `Query` version declares it explicitly as being a query parameter.
Then, we can pass more parameters to `Query`. In this case, the `max_length` parameter that applies to strings:
```Python
-q: Union[str, None] = Query(default=None, max_length=50)
+q: str | None = Query(default=None, max_length=50)
```
This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema *path operation*.
@@ -201,7 +150,7 @@ This will validate the data, show a clear error when the data is not valid, and
Keep in mind that when using `Query` inside of `Annotated` you cannot use the `default` parameter for `Query`.
-Instead use the actual default value of the function parameter. Otherwise, it would be inconsistent.
+Instead, use the actual default value of the function parameter. Otherwise, it would be inconsistent.
For example, this is not allowed:
@@ -255,7 +204,7 @@ This specific regular expression pattern checks that the received parameter valu
If you feel lost with all these **"regular expression"** ideas, don't worry. They are a hard topic for many people. You can still do a lot of stuff without needing regular expressions yet.
-But whenever you need them and go and learn them, know that you can already use them directly in **FastAPI**.
+Now you know that whenever you need them you can use them in **FastAPI**.
### Pydantic v1 `regex` instead of `pattern`
@@ -296,7 +245,7 @@ q: str
instead of:
```Python
-q: Union[str, None] = None
+q: str | None = None
```
But we are now declaring it with `Query`, for example like:
@@ -304,15 +253,7 @@ But we are now declaring it with `Query`, for example like:
//// tab | Annotated
```Python
-q: Annotated[Union[str, None], Query(min_length=3)] = None
-```
-
-////
-
-//// tab | non-Annotated
-
-```Python
-q: Union[str, None] = Query(default=None, min_length=3)
+q: Annotated[str | None, Query(min_length=3)] = None
```
////
@@ -321,42 +262,14 @@ So, when you need to declare a value as required while using `Query`, you can si
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
-### Required with Ellipsis (`...`)
-
-There's an alternative way to explicitly declare that a value is required. You can set the default to the literal value `...`:
-
-{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *}
-
-/// info
-
-If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis".
-
-It is used by Pydantic and FastAPI to explicitly declare that a value is required.
-
-///
-
-This will let **FastAPI** know that this parameter is required.
-
### Required, can be `None`
You can declare that a parameter can accept `None`, but that it's still required. This would force clients to send a value, even if the value is `None`.
-To do that, you can declare that `None` is a valid type but still use `...` as the default:
+To do that, you can declare that `None` is a valid type but simply do not declare a default value:
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
-/// tip
-
-Pydantic, which is what powers all the data validation and serialization in FastAPI, has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about Required fields.
-
-///
-
-/// tip
-
-Remember that in most of the cases, when something is required, you can simply omit the default, so you normally don't have to use `...`.
-
-///
-
## Query parameter list / multiple values
When you define a query parameter explicitly with `Query` you can also declare it to receive a list of values, or said in another way, to receive multiple values.
@@ -396,7 +309,7 @@ The interactive API docs will update accordingly, to allow multiple values:
### Query parameter list / multiple values with defaults
-And you can also define a default `list` of values if none are provided:
+You can also define a default `list` of values if none are provided:
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
@@ -419,7 +332,7 @@ the default of `q` will be: `["foo", "bar"]` and your response will be:
#### Using just `list`
-You can also use `list` directly instead of `List[str]` (or `list[str]` in Python 3.9+):
+You can also use `list` directly instead of `list[str]`:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
@@ -427,7 +340,7 @@ You can also use `list` directly instead of `List[str]` (or `list[str]` in Pytho
Keep in mind that in this case, FastAPI won't check the contents of the list.
-For example, `List[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't.
+For example, `list[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't.
///
diff --git a/docs/en/docs/tutorial/response-status-code.md b/docs/en/docs/tutorial/response-status-code.md
index 711042a46..41bf02a8f 100644
--- a/docs/en/docs/tutorial/response-status-code.md
+++ b/docs/en/docs/tutorial/response-status-code.md
@@ -53,16 +53,16 @@ These status codes have a name associated to recognize them, but the important p
In short:
-* `100` and above are for "Information". You rarely use them directly. Responses with these status codes cannot have a body.
-* **`200`** and above are for "Successful" responses. These are the ones you would use the most.
+* `100 - 199` are for "Information". You rarely use them directly. Responses with these status codes cannot have a body.
+* **`200 - 299`** are for "Successful" responses. These are the ones you would use the most.
* `200` is the default status code, which means everything was "OK".
* Another example would be `201`, "Created". It is commonly used after creating a new record in the database.
* A special case is `204`, "No Content". This response is used when there is no content to return to the client, and so the response must not have a body.
-* **`300`** and above are for "Redirection". Responses with these status codes may or may not have a body, except for `304`, "Not Modified", which must not have one.
-* **`400`** and above are for "Client error" responses. These are the second type you would probably use the most.
+* **`300 - 399`** are for "Redirection". Responses with these status codes may or may not have a body, except for `304`, "Not Modified", which must not have one.
+* **`400 - 499`** are for "Client error" responses. These are the second type you would probably use the most.
* An example is `404`, for a "Not Found" response.
* For generic errors from the client, you can just use `400`.
-* `500` and above are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes.
+* `500 - 599` are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes.
/// tip
diff --git a/docs/es/docs/tutorial/query-params-str-validations.md b/docs/es/docs/tutorial/query-params-str-validations.md
index f378b9dce..9cb76156f 100644
--- a/docs/es/docs/tutorial/query-params-str-validations.md
+++ b/docs/es/docs/tutorial/query-params-str-validations.md
@@ -321,22 +321,6 @@ Asรญ que, cuando necesites declarar un valor como requerido mientras usas `Query
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
-### Requerido con Puntos suspensivos (`...`)
-
-Hay una manera alternativa de declarar explรญcitamente que un valor es requerido. Puedes establecer el valor por defecto al valor literal `...`:
-
-{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *}
-
-/// info | Informaciรณn
-
-Si no habรญas visto eso `...` antes: es un valor especial รบnico, es parte de Python y se llama "Ellipsis".
-
-Se usa por Pydantic y FastAPI para declarar explรญcitamente que un valor es requerido.
-
-///
-
-Esto le permitirรก a **FastAPI** saber que este parรกmetro es requerido.
-
### Requerido, puede ser `None`
Puedes declarar que un parรกmetro puede aceptar `None`, pero que aรบn asรญ es requerido. Esto obligarรญa a los clientes a enviar un valor, incluso si el valor es `None`.
diff --git a/docs/ja/docs/environment-variables.md b/docs/ja/docs/environment-variables.md
new file mode 100644
index 000000000..507af3a0c
--- /dev/null
+++ b/docs/ja/docs/environment-variables.md
@@ -0,0 +1,301 @@
+# ็ฐๅขๅคๆฐ
+
+/// tip
+
+ใใใใ็ฐๅขๅคๆฐใใจใฏไฝใใใใใใฉใไฝฟใใใๆขใซ็ฅใฃใฆใใๅ ดๅใฏใใใฎใปใฏใทใงใณใในใญใใใใฆๆงใใพใใใ
+
+///
+
+็ฐๅขๅคๆฐ๏ผ**env var**ใจใๅผใฐใใ๏ผใฏPythonใณใผใใฎ**ๅคๅด**ใใคใพใ**OS**ใซๅญๅจใใๅคๆฐใงใPythonใใ่ชญใฟๅใใใจใใงใใพใใ๏ผไปใฎใใญใฐใฉใ ใงใๅๆงใซ่ชญใฟๅใใพใใ๏ผ
+
+็ฐๅขๅคๆฐใฏใใขใใชใฑใผใทใงใณใฎ**่จญๅฎ**ใฎ็ฎก็ใใPythonใฎ**ใคใณในใใผใซ**ใชใฉใซๅฝน็ซใกใพใใ
+
+## ็ฐๅขๅคๆฐใฎไฝๆใจไฝฟ็จ
+
+็ฐๅขๅคๆฐใฏ**ใทใงใซ๏ผใฟใผใใใซ๏ผ**ๅ
ใง**ไฝๆ**ใใฆไฝฟ็จใงใใใใใใซPythonใฏไธ่ฆใงใใ
+
+//// tab | Linux, macOS, Windows Bash
+
+
+
+```console
+// You could create an env var MY_NAME with
+$ export MY_NAME="Wade Wilson"
+
+// Then you could use it with other programs, like
+$ echo "Hello $MY_NAME"
+
+Hello Wade Wilson
+```
+
+
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+
+```console
+// Create an env var MY_NAME
+$ $Env:MY_NAME = "Wade Wilson"
+
+// Use it with other programs, like
+$ echo "Hello $Env:MY_NAME"
+
+Hello Wade Wilson
+```
+
+
+
+////
+
+## Pythonใง็ฐๅขๅคๆฐใ่ชญใฟๅใ
+
+็ฐๅขๅคๆฐใPythonใฎ**ๅคๅด**ใใฟใผใใใซ๏ผใไปใฎๆนๆณ๏ผใงไฝๆใใ**Pythonๅ
ใง่ชญใฟๅใ**ใใจใใงใใพใใ
+
+ไพใใฐใไปฅไธใฎใใใช`main.py`ใใกใคใซใ็จๆใใพใ:
+
+```Python hl_lines="3"
+import os
+
+name = os.getenv("MY_NAME", "World")
+print(f"Hello {name} from Python")
+```
+
+/// tip
+
+
`os.getenv()` ใฎ็ฌฌ2ๅผๆฐใฏใใใใฉใซใใง่ฟใใใๅคใๆๅฎใใพใใ
+
+ใใฎๅผๆฐใ็็ฅใใใจใใใฉใซใๅคใจใใฆ`None`ใ่ฟใใใพใใใใใใงใฏใใใฉใซใๅคใจใใฆ`"World"`ใๆๅฎใใฆใใพใใ
+
+///
+
+ๆฌกใซใใใฎPythonใใญใฐใฉใ ใๅผใณๅบใใพใใ
+
+//// tab | Linux, macOS, Windows Bash
+
+
+
+```console
+// Here we don't set the env var yet
+$ python main.py
+
+// As we didn't set the env var, we get the default value
+
+Hello World from Python
+
+// But if we create an environment variable first
+$ export MY_NAME="Wade Wilson"
+
+// And then call the program again
+$ python main.py
+
+// Now it can read the environment variable
+
+Hello Wade Wilson from Python
+```
+
+
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+```console
+// Here we don't set the env var yet
+$ python main.py
+
+// As we didn't set the env var, we get the default value
+
+Hello World from Python
+
+// But if we create an environment variable first
+$ $Env:MY_NAME = "Wade Wilson"
+
+// And then call the program again
+$ python main.py
+
+// Now it can read the environment variable
+
+Hello Wade Wilson from Python
+```
+
+
+
+////
+
+็ฐๅขๅคๆฐใฏใณใผใใฎๅคๅดใง่จญๅฎใใๅ
ๅดใใ่ชญใฟๅใใใจใใงใใใฎใงใไปใฎใใกใคใซใจไธ็ทใซ๏ผ`git`ใซ๏ผไฟๅญใใๅฟ
่ฆใใใใพใใใใใฎใใใ็ฐๅขๅคๆฐใใณใณใใฃใฐใฌใผใทใงใณใ**่จญๅฎ**ใซไฝฟ็จใใใใจใไธ่ฌ็ใงใใ
+
+ใพใใ**็นๅฎใฎใใญใฐใฉใ ใฎๅผใณๅบใ**ใฎใใใฎ็ฐๅขๅคๆฐใใใใฎใใญใฐใฉใ ใฎใฟใใใฎๅฎ่กไธญใซ้ๅฎใใฆๅฉ็จใงใใใใไฝๆใงใใพใใ
+
+ใใฎใใใซใฏใใใญใฐใฉใ ่ตทๅใณใใณใใจๅใใณใใณใใฉใคใณไธใฎใ่ตทๅใณใใณใ็ดๅใง็ฐๅขๅคๆฐใไฝๆใใฆใใ ใใใ
+
+
+
+```console
+// Create an env var MY_NAME in line for this program call
+$ MY_NAME="Wade Wilson" python main.py
+
+// Now it can read the environment variable
+
+Hello Wade Wilson from Python
+
+// The env var no longer exists afterwards
+$ python main.py
+
+Hello World from Python
+```
+
+
+
+/// tip
+
+่ฉณใใใฏ
The Twelve-Factor App: Config ใๅ็
งใใฆใใ ใใใ
+
+///
+
+## ๅใจใใชใใผใทใงใณ
+
+็ฐๅขๅคๆฐใฏ**ใใญในใๆๅญๅ**ใฎใฟใๆฑใใใจใใงใใพใใใใใฏใ็ฐๅขๅคๆฐใPythonๅค้จใซๅญๅจใใไปใฎใใญใฐใฉใ ใใทในใใ ๅ
จไฝ๏ผLinuxใWindowsใmacOS้ใฎไบๆๆงใๅซใ๏ผใจ้ฃๆบใใๅฟ
่ฆใใใใใใงใใ
+
+ใคใพใใPythonใ็ฐๅขๅคๆฐใใ่ชญใฟๅใ**ใใใใๅค**ใฏ **`str`ๅใจใชใ**ใไปใฎๅใธใฎๅคๆใใใชใใผใทใงใณใฏใณใผใๅ
ใง่กใๅฟ
่ฆใใใใพใใ
+
+็ฐๅขๅคๆฐใไฝฟ็จใใฆ**ใขใใชใฑใผใทใงใณ่จญๅฎ**ใ็ฎก็ใใๆนๆณใซใคใใฆใฏใ[้ซๅบฆใชใฆใผใถใผใฌใคใ - Settings and Environment Variables](./advanced/settings.md){.internal-link target=_blank}ใง่ฉณใใๅญฆในใพใใ
+
+## `PATH`็ฐๅขๅคๆฐ
+
+**`PATH`**ใจใใ**็นๅฅใช**็ฐๅขๅคๆฐใใใใพใใใใฎ็ฐๅขๅคๆฐใฏใOS๏ผLinuxใmacOSใWindows๏ผใๅฎ่กใใใใญใฐใฉใ ใ็บ่ฆใใใใใซไฝฟ็จใใใพใใ
+
+`PATH`ๅคๆฐใฏใ่คๆฐใฎใใฃใฌใฏใใชใฎใในใใๆใ้ทใๆๅญๅใงใใใใฎใในใฏLinuxใMacOSใฎๅ ดๅใฏ`:`ใงใWindowsใฎๅ ดๅใฏ`;`ใงๅบๅใใใฆใใพใใ
+
+ไพใใฐใ`PATH`็ฐๅขๅคๆฐใฏๆฌกใฎใใใชๆๅญๅใใใใใพใใ:
+
+//// tab | Linux, macOS
+
+```plaintext
+/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+```
+
+ใใใฏใOSใฏใใญใฐใฉใ ใ่ฆใคใใใใใซไปฅไธใฎใใฃใฌใฏใใชใๆขใใใจใใใใจใๆๅณใใพใ:
+
+* `/usr/local/bin`
+* `/usr/bin`
+* `/bin`
+* `/usr/sbin`
+* `/sbin`
+
+////
+
+//// tab | Windows
+
+```plaintext
+C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
+```
+
+ใใใฏใOSใฏใใญใฐใฉใ ใ่ฆใคใใใใใซไปฅไธใฎใใฃใฌใฏใใชใๆขใใใจใใใใจใๆๅณใใพใ:
+
+* `C:\Program Files\Python312\Scripts`
+* `C:\Program Files\Python312`
+* `C:\Windows\System32`
+
+////
+
+ใฟใผใใใซไธใง**ใณใใณใ**ใๅ
ฅๅใใใจใ OSใฏใใฎใใญใฐใฉใ ใ่ฆใคใใใใใซใ`PATH`็ฐๅขๅคๆฐใฎใชในใใซ่จ่ผใใใ**ใใใใใฎใใฃใฌใฏใใชใๆขใ**ใพใใ
+
+ไพใใฐใใฟใผใใใซไธใง`python`ใๅ
ฅๅใใใจใOSใฏ`python`ใซใใฃใฆๅผใฐใใใใญใฐใฉใ ใ่ฆใคใใใใใซใใใฎใชในใใฎ**ๅ
้ ญใฎใใฃใฌใฏใใช**ใๆๅใซๆขใใพใใ
+
+OSใฏใใใใใฎใใญใฐใฉใ ใใใใง็บ่ฆใใใฐ**ๅฎ่กใ**ใพใใใใใใงใชใใใฐใชในใใฎ**ไปใฎใใฃใฌใฏใใช**ใๆขใใฆใใใพใใ
+
+### PythonใฎใคใณในใใผใซใจPATH็ฐๅขๅคๆฐใฎๆดๆฐ
+
+Pythonใฎใคใณในใใผใซๆใซ`PATH`็ฐๅขๅคๆฐใๆดๆฐใใใใ่ใใใใใใใใพใใใ
+
+/// tab | Linux, macOS
+
+Pythonใใคใณในใใผใซใใฆใใใฎใใญใฐใฉใ ใ`/opt/custompython/bin`ใจใใใใฃใฌใฏใใชใซ้
็ฝฎใใใใจใใพใใ
+
+ใใใ`PATH`็ฐๅขๅคๆฐใๆดๆฐใใใใใซ็ญใใใจใ`PATH`็ฐๅขๅคๆฐใซ`/opt/custompython/bin`ใ่ฟฝๅ ใใใพใใ
+
+`PATH`็ฐๅขๅคๆฐใฏไปฅไธใฎใใใซๆดๆฐใใใใงใใใ๏ผ
+
+``` plaintext
+/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
+```
+
+ใใฎใใใซใใฆใใฟใผใใใซใง`python`ใจๅ
ฅๅใใใจใใซใOSใฏ`/opt/custompython/bin`๏ผใชในใใฎๆซๅฐพใฎใใฃใฌใฏใใช๏ผใซใใPythonใใญใฐใฉใ ใ่ฆใคใใไฝฟ็จใใพใใ
+
+///
+
+/// tab | Windows
+
+Pythonใใคใณในใใผใซใใฆใใใฎใใญใฐใฉใ ใ`C:\opt\custompython\bin`ใจใใใใฃใฌใฏใใชใซ้
็ฝฎใใใใจใใพใใ
+
+ใใใ`PATH`็ฐๅขๅคๆฐใๆดๆฐใใใใใซ็ญใใใจใ`PATH`็ฐๅขๅคๆฐใซ`C:\opt\custompython\bin`ใ่ฟฝๅ ใใใพใใ
+
+`PATH`็ฐๅขๅคๆฐใฏไปฅไธใฎใใใซๆดๆฐใใใใงใใใ๏ผ
+
+```plaintext
+C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
+```
+
+ใใฎใใใซใใฆใใฟใผใใใซใง`python`ใจๅ
ฅๅใใใจใใซใOSใฏ`C:\opt\custompython\bin\python`๏ผใชในใใฎๆซๅฐพใฎใใฃใฌใฏใใช๏ผใซใใPythonใใญใฐใฉใ ใ่ฆใคใใไฝฟ็จใใพใใ
+
+///
+
+ใคใพใใใฟใผใใใซใงไปฅไธใฎใณใใณใใๅ
ฅๅใใใจ๏ผ
+
+
+
+``` console
+$ python
+```
+
+
+
+/// tab | Linux, macOS
+
+OSใฏ`/opt/custompython/bin`ใซใใ`python`ใใญใฐใฉใ ใ**่ฆใคใ**ใฆๅฎ่กใใพใใ
+
+ใใใฏใๆฌกใฎใณใใณใใๅ
ฅๅใใๅ ดๅใจใปใจใใฉๅ็ญใงใ๏ผ
+
+
+
+```console
+$ /opt/custompython/bin/python
+```
+
+
+
+///
+
+/// tab | Windows
+
+OSใฏ`C:\opt\custompython\bin\python`ใซใใ`python`ใใญใฐใฉใ ใ**่ฆใคใ**ใฆๅฎ่กใใพใใ
+
+ใใใฏใๆฌกใฎใณใใณใใๅ
ฅๅใใๅ ดๅใจใปใจใใฉๅ็ญใงใ๏ผ
+
+
+
+```console
+$ C:\opt\custompython\bin\python
+```
+
+
+
+///
+
+ใใฎๆ
ๅ ฑใฏใ[Virtual Environments](virtual-environments.md) ใซใคใใฆๅญฆใถ้ใซใๅฝน็ซใกใพใใ
+
+## ใพใจใ
+
+ใใใงใ**็ฐๅขๅคๆฐ**ใจใฏไฝใใPythonใงใฉใฎใใใซไฝฟ็จใใใใซใคใใฆใๅบๆฌ็ใช็่งฃใๅพใใใใฏใใงใใ
+
+็ฐๅขๅคๆฐใซใคใใฆใฎ่ฉณ็ดฐใฏใ
Wikipedia: Environment Variable ใๅ็
งใใฆใใ ใใใ
+
+็ฐๅขๅคๆฐใฎ็จ้ใ้ฉ็จๆนๆณใๆๅใฏ็ดๆ็ใงใฏใชใใใใใใพใใใใ้็บไธญใฎใใพใใพใชใทใใชใชใง็นฐใ่ฟใ็ปๅ ดใใพใใใใฎใใใๅบๆฌใ็ฅใฃใฆใใใใจใ้่ฆใงใใ
+
+ใใจใใฐใใใฎๆ
ๅ ฑใฏๆฌกใฎใปใฏใทใงใณใงๆฑใ[Virtual Environments](virtual-environments.md)ใซใ้ข้ฃใใพใใ
diff --git a/docs/ko/docs/advanced/custom-response.md b/docs/ko/docs/advanced/custom-response.md
new file mode 100644
index 000000000..2001956fa
--- /dev/null
+++ b/docs/ko/docs/advanced/custom-response.md
@@ -0,0 +1,313 @@
+# ์ฌ์ฉ์ ์ ์ ์๋ต - HTML, Stream, ํ์ผ, ๊ธฐํ
+
+๊ธฐ๋ณธ์ ์ผ๋ก, **FastAPI** ์๋ต์ `JSONResponse`๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐํํฉ๋๋ค.
+
+์ด๋ฅผ ์ฌ์ ์ ํ๋ ค๋ฉด [์๋ต์ ์ง์ ๋ฐํํ๊ธฐ](response-directly.md){.internal-link target=_blank}์์ ๋ณธ ๊ฒ์ฒ๋ผ `Response`๋ฅผ ์ง์ ๋ฐํํ๋ฉด ๋ฉ๋๋ค.
+
+๊ทธ๋ฌ๋ `Response` (๋๋ `JSONResponse`์ ๊ฐ์ ํ์ ํด๋์ค)๋ฅผ ์ง์ ๋ฐํํ๋ฉด, ๋ฐ์ดํฐ๊ฐ ์๋์ผ๋ก ๋ณํ๋์ง ์์ผ๋ฉฐ (์ฌ์ง์ด `response_model`์ ์ ์ธํ๋๋ผ๋), ๋ฌธ์ํ๊ฐ ์๋์ผ๋ก ์์ฑ๋์ง ์์ต๋๋ค(์๋ฅผ ๋ค์ด, ์์ฑ๋ OpenAPI์ ์ผ๋ถ๋ก HTTP ํค๋ `Content-Type`์ ํน์ "๋ฏธ๋์ด ํ์
"์ ํฌํจํ๋ ๊ฒฝ์ฐ).
+
+ํ์ง๋ง *๊ฒฝ๋ก ์์
๋ฐ์ฝ๋ ์ดํฐ*์์ `response_class` ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ์ํ๋ `Response`(์: ๋ชจ๋ `Response` ํ์ ํด๋์ค)๋ฅผ ์ ์ธํ ์๋ ์์ต๋๋ค.
+
+*๊ฒฝ๋ก ์์
ํจ์*์์ ๋ฐํํ๋ ๋ด์ฉ์ ํด๋น `Response`์์ ํฌํจ๋ฉ๋๋ค.
+
+๊ทธ๋ฆฌ๊ณ ๋ง์ฝ ๊ทธ `Response`๊ฐ `JSONResponse`์ `UJSONResponse`์ ๊ฒฝ์ฐ ์ฒ๋ผ JSON ๋ฏธ๋์ด ํ์
(`application/json`)์ ๊ฐ์ง๊ณ ์๋ค๋ฉด, *๊ฒฝ๋ก ์์
๋ฐ์ฝ๋ ์ดํฐ*์์ ์ ์ธํ Pydantic์ `response_model`์ ์ฌ์ฉํด ์๋์ผ๋ก ๋ณํ(๋ฐ ํํฐ๋ง) ๋ฉ๋๋ค.
+
+/// note | ์ฐธ๊ณ
+
+๋ฏธ๋์ด ํ์
์ด ์๋ ์๋ต ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, FastAPI๋ ์๋ต์ ๋ด์ฉ์ด ์์ ๊ฒ์ผ๋ก ์์ํ๋ฏ๋ก ์์ฑ๋ OpenAPI ๋ฌธ์์์ ์๋ต ํ์์ ๋ฌธ์ํํ์ง ์์ต๋๋ค.
+
+///
+
+## `ORJSONResponse` ์ฌ์ฉํ๊ธฐ
+
+์๋ฅผ ๋ค์ด, ์ฑ๋ฅ์ ๊ทน๋ํํ๋ ค๋ ๊ฒฝ์ฐ,
orjson์ ์ค์นํ์ฌ ์ฌ์ฉํ๊ณ ์๋ต์ `ORJSONResponse`๋ก ์ค์ ํ ์ ์์ต๋๋ค.
+
+์ฌ์ฉํ๊ณ ์ ํ๋ `Response` ํด๋์ค(ํ์ ํด๋์ค)๋ฅผ ์ํฌํธํ ํ, **๊ฒฝ๋ก ์์
๋ฐ์ฝ๋ ์ดํฐ*์์ ์ ์ธํ์ธ์.
+
+๋๊ท๋ชจ ์๋ต์ ๊ฒฝ์ฐ, ๋์
๋๋ฆฌ๋ฅผ ๋ฐํํ๋ ๊ฒ๋ณด๋ค `Response`๋ฅผ ๋ฐํํ๋ ๊ฒ์ด ํจ์ฌ ๋น ๋ฆ
๋๋ค.
+
+์ด์ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก, FastAPI๊ฐ ๋ด๋ถ์ ๋ชจ๋ ํญ๋ชฉ์ ๊ฒ์ฌํ๊ณ JSON์ผ๋ก ์ง๋ ฌํํ ์ ์๋์ง ํ์ธํ๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด๋ ์ฌ์ฉ์ ์๋ด์์์ ์ค๋ช
๋ [JSON ํธํ ๊ฐ๋ฅ ์ธ์ฝ๋](../tutorial/encoder.md){.internal-link target=_blank}๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์๊ณผ ๋์ผํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ๊ณผ ๊ฐ์ **์์์ ๊ฐ์ฒด**๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค.
+
+ํ์ง๋ง ๋ฐํํ๋ ๋ด์ฉ์ด **JSON์ผ๋ก ์ง๋ ฌํ ๊ฐ๋ฅ**ํ๋ค๊ณ ํ์ ํ๋ ๊ฒฝ์ฐ, ํด๋น ๋ด์ฉ์ ์๋ต ํด๋์ค์ ์ง์ ์ ๋ฌํ ์ ์์ผ๋ฉฐ, FastAPI๊ฐ ๋ฐํ ๋ด์ฉ์ `jsonable_encoder`๋ฅผ ํตํด ์ฒ๋ฆฌํ ๋ค ์๋ต ํด๋์ค์ ์ ๋ฌํ๋ ์ค๋ฒํค๋๋ฅผ ํผํ ์ ์์ต๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
+
+/// info | ์ ๋ณด
+
+`response_class` ๋งค๊ฐ๋ณ์๋ ์๋ต์ "๋ฏธ๋์ด ํ์
"์ ์ ์ํ๋ ๋ฐ์๋ ์ฌ์ฉ๋ฉ๋๋ค.
+
+์ด ๊ฒฝ์ฐ, HTTP ํค๋ `Content-Type`์ `application/json`์ผ๋ก ์ค์ ๋ฉ๋๋ค.
+
+๊ทธ๋ฆฌ๊ณ ์ด๋ OpenAPI์ ๊ทธ๋๋ก ๋ฌธ์ํ๋ฉ๋๋ค.
+
+///
+
+/// tip | ํ
+
+`ORJSONResponse`๋ FastAPI์์๋ง ์ฌ์ฉํ ์ ์๊ณ Starlette์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+///
+
+## HTML ์๋ต
+
+**FastAPI**์์ HTML ์๋ต์ ์ง์ ๋ฐํํ๋ ค๋ฉด `HTMLResponse`๋ฅผ ์ฌ์ฉํ์ธ์.
+
+* `HTMLResponse`๋ฅผ ์ํฌํธ ํฉ๋๋ค.
+* *๊ฒฝ๋ก ์์
๋ฐ์ฝ๋ ์ดํฐ*์ `response_class` ๋งค๊ฐ๋ณ์๋ก `HTMLResponse`๋ฅผ ์ ๋ฌํฉ๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
+
+/// info | ์ ๋ณด
+
+`response_class` ๋งค๊ฐ๋ณ์๋ ์๋ต์ "๋ฏธ๋์ด ํ์
"์ ์ ์ํ๋ ๋ฐ์๋ ์ฌ์ฉ๋ฉ๋๋ค.
+
+์ด ๊ฒฝ์ฐ, HTTP ํค๋ `Content-Type`์ `text/html`๋ก ์ค์ ๋ฉ๋๋ค.
+
+๊ทธ๋ฆฌ๊ณ ์ด๋ OpenAPI์ ๊ทธ๋๋ก ๋ฌธ์ํ ๋ฉ๋๋ค.
+
+///
+
+### `Response` ๋ฐํํ๊ธฐ
+
+[์๋ต์ ์ง์ ๋ฐํํ๊ธฐ](response-directly.md){.internal-link target=_blank}์์ ๋ณธ ๊ฒ ์ฒ๋ผ, *๊ฒฝ๋ก ์์
*์์ ์๋ต์ ์ง์ ๋ฐํํ์ฌ ์ฌ์ ์ํ ์๋ ์์ต๋๋ค.
+
+์์ ์์ ์ ๋์ผํ๊ฒ `HTMLResponse`๋ฅผ ๋ฐํํ๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ ์ ์์ต๋๋ค:
+
+{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
+
+/// warning | ๊ฒฝ๊ณ
+
+*๊ฒฝ๋ก ์์
ํจ์*์์ ์ง์ ๋ฐํ๋ `Response`๋ OpenAPI์ ๋ฌธ์ํ๋์ง ์์ต๋๋ค(์๋ฅผ๋ค์ด, `Content-Type`์ด ๋ฌธ์ํ๋์ง ์์) ์๋ ๋ํํ ๋ฌธ์์์๋ ํ์๋์ง ์์ต๋๋ค.
+
+///
+
+/// info | ์ ๋ณด
+
+๋ฌผ๋ก ์ค์ `Content-Type` ํค๋, ์ํ ์ฝ๋ ๋ฑ์ ๋ฐํ๋ `Response` ๊ฐ์ฒด์์ ๊ฐ์ ธ์ต๋๋ค.
+
+///
+
+### OpenAPI์ ๋ฌธ์ํํ๊ณ `Response` ์ฌ์ ์ ํ๊ธฐ
+
+ํจ์ ๋ด๋ถ์์ ์๋ต์ ์ฌ์ ์ํ๋ฉด์ ๋์์ OpenAPI์์ "๋ฏธ๋์ด ํ์
"์ ๋ฌธ์ํํ๊ณ ์ถ๋ค๋ฉด, `response_class` ๋งค๊ฒ๋ณ์๋ฅผ ์ฌ์ฉํ๋ฉด์ `Response` ๊ฐ์ฒด๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค.
+
+์ด ๊ฒฝ์ฐ `response_class`๋ OpenAPI *๊ฒฝ๋ก ์์
*์ ๋ฌธ์ํํ๋ ๋ฐ๋ง ์ฌ์ฉ๋๊ณ , ์ค์ ๋ก๋ ์ฌ๋ฌ๋ถ์ด ๋ฐํํ `Response`๊ฐ ๊ทธ๋๋ก ์ฌ์ฉ๋ฉ๋๋ค.
+
+### `HTMLResponse`์ง์ ๋ฐํํ๊ธฐ
+
+์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ ์ ์์ต๋๋ค:
+
+{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
+
+์ด ์์ ์์, `generate_html_response()` ํจ์๋ HTML์ `str`๋ก ๋ฐํํ๋ ๋์ ์ด๋ฏธ `Response`๋ฅผ ์์ฑํ๊ณ ๋ฐํํฉ๋๋ค.
+
+`generate_html_response()`๋ฅผ ํธ์ถํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํจ์ผ๋ก์จ, ๊ธฐ๋ณธ์ ์ธ **FastAPI** ๊ธฐ๋ณธ ๋์์ ์ฌ์ ์ ํ๋ `Response`๋ฅผ ์ด๋ฏธ ๋ฐํํ๊ณ ์์ต๋๋ค.
+
+ํ์ง๋ง `response_class`์ `HTMLResponse`๋ฅผ ํจ๊ป ์ ๋ฌํ๊ธฐ ๋๋ฌธ์, FastAPI๋ ์ด๋ฅผ OpenAPI ๋ฐ ๋ํํ ๋ฌธ์์์ `text/html`๋ก HTML์ ๋ฌธ์ํ ํ๋ ๋ฐฉ๋ฒ์ ์ ์ ์์ต๋๋ค.
+
+

+
+## ์ฌ์ฉ ๊ฐ๋ฅํ ์๋ต๋ค
+
+๋ค์์ ์ฌ์ฉํ ์ ์๋ ๋ช๊ฐ์ง ์๋ต๋ค ์
๋๋ค.
+
+`Response`๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ์ด๋ค ๊ฒ๋ ๋ฐํ ํ ์ ์์ผ๋ฉฐ, ์ง์ ํ์ ํด๋์ค๋ฅผ ๋ง๋ค ์๋ ์์ต๋๋ค.
+
+/// note | ๊ธฐ์ ์ธ๋ถ์ฌํญ
+
+`from starlette.responses import HTMLResponse`๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
+
+**FastAPI**๋ ๊ฐ๋ฐ์์ธ ์ฌ๋ฌ๋ถ์ ํธ์๋ฅผ ์ํด `starlette.responses`๋ฅผ `fastapi.responses`๋ก ์ ๊ณต ํ์ง๋ง, ๋๋ถ๋ถ์ ์ฌ์ฉ ๊ฐ๋ฅํ ์๋ต์ Starlette์์ ์ง์ ๊ฐ์ ธ์ต๋๋ค.
+
+///
+
+### `Response`
+
+๊ธฐ๋ณธ `Response` ํด๋์ค๋ ๋ค๋ฅธ ๋ชจ๋ ์๋ต ํด๋์ค์ ๋ถ๋ชจ ํด๋์ค ์
๋๋ค.
+
+์ด ํด๋์ค๋ฅผ ์ง์ ๋ฐํํ ์ ์์ต๋๋ค.
+
+๋ค์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค:
+
+* `content` - `str` ๋๋ `bytes`.
+* `status_code` - HTTP ์ํ์ฝ๋๋ฅผ ๋ํ๋ด๋ `int`.
+* `headers` - ๋ฌธ์์ด๋ก ์ด๋ฃจ์ด์ง `dict`.
+* `media_type` - ๋ฏธ๋์ด ํ์
์ ๋ํ๋ด๋ `str` ์: `"text/html"`.
+
+FastAPI (์ค์ ๋ก๋ Starlette)๊ฐ ์๋์ผ๋ก `Content-Length` ํค๋๋ฅผ ํฌํจ์ํต๋๋ค. ๋ํ `media_type`์ ๊ธฐ๋ฐํ์ฌ `Content-Type` ํค๋๋ฅผ ํฌํจํ๋ฉฐ, ํ
์คํธ ํ์
์ ๊ฒฝ์ฐ ๋ฌธ์ ์งํฉ์ ์ถ๊ฐ ํฉ๋๋ค.
+
+{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
+
+### `HTMLResponse`
+
+ํ
์คํธ ๋๋ ๋ฐ์ดํธ๋ฅผ ๋ฐ์ HTML ์๋ต์ ๋ฐํํฉ๋๋ค. ์์์ ์ค๋ช
ํ ๋ด์ฉ๊ณผ ๊ฐ์ต๋๋ค.
+
+### `PlainTextResponse`
+
+ํ
์คํธ ๋๋ ๋ฐ์ดํธ๋ฅผ ๋ฐ์ ์ผ๋ฐ ํ
์คํธ ์๋ต์ ๋ฐํํฉ๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
+
+### `JSONResponse`
+
+๋ฐ์ดํฐ๋ฅผ ๋ฐ์ `application/json`์ผ๋ก ์ธ์ฝ๋ฉ๋ ์๋ต์ ๋ฐํํฉ๋๋ค.
+
+์ด๋ ์์์ ์ค๋ช
ํ๋ฏ์ด **FastAPI**์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ๋๋ ์๋ต ํ์์
๋๋ค.
+
+### `ORJSONResponse`
+
+
`orjson`์ ์ฌ์ฉํ์ฌ ๋น ๋ฅธ JSON ์๋ต์ ์ ๊ณตํ๋ ๋์์
๋๋ค. ์์์ ์ค๋ช
ํ ๋ด์ฉ๊ณผ ๊ฐ์ต๋๋ค.
+
+/// info | ์ ๋ณด
+
+์ด๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด `orjson`์ ์ค์นํด์ผํฉ๋๋ค. ์: `pip install orjson`.
+
+///
+
+### `UJSONResponse`
+
+
`ujson`์ ์ฌ์ฉํ ๋ ๋ค๋ฅธ JSON ์๋ต ํ์์
๋๋ค.
+
+/// info | ์ ๋ณด
+
+์ด ์๋ต์ ์ฌ์ฉํ๋ ค๋ฉด `ujson`์ ์ค์นํด์ผํฉ๋๋ค. ์: 'pip install ujson`.
+
+///
+
+/// warning | ๊ฒฝ๊ณ
+
+`ujson` ์ ์ผ๋ถ ์์ธ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ ์์ด Python ๋ด์ฅ ๊ตฌํ๋ณด๋ค ๋ ์๊ฒฉํฉ๋๋ค.
+
+///
+
+{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
+
+/// tip | ํ
+
+`ORJSONResponse`๊ฐ ๋ ๋น ๋ฅธ ๋์์ผ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
+
+///
+
+### `RedirectResponse`
+
+HTTP ๋ฆฌ๋๋ ์
์๋ต์ ๋ฐํํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ์ํ ์ฝ๋๋ 307(์์ ๋ฆฌ๋๋ ์
)์ผ๋ก ์ค์ ๋ฉ๋๋ค.
+
+`RedirectResponse`๋ฅผ ์ง์ ๋ฐํํ ์ ์์ต๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
+
+---
+
+๋๋ `response_class` ๋งค๊ฐ๋ณ์์์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค:
+
+
+{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
+
+์ด ๊ฒฝ์ฐ, *๊ฒฝ๋ก ์์
* ํจ์์์ URL์ ์ง์ ๋ฐํํ ์ ์์ต๋๋ค.
+
+์ด ๊ฒฝ์ฐ, ์ฌ์ฉ๋๋ `status_code`๋ `RedirectResponse`์ ๊ธฐ๋ณธ๊ฐ์ธ `307` ์
๋๋ค.
+
+---
+
+`status_code` ๋งค๊ฐ๋ณ์๋ฅผ `response_class` ๋งค๊ฐ๋ณ์์ ํจ๊ป ์ฌ์ฉํ ์๋ ์์ต๋๋ค:
+
+{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
+
+### `StreamingResponse`
+
+๋น๋๊ธฐ ์ ๋๋ ์ดํฐ ๋๋ ์ผ๋ฐ ์ ๋๋ ์ดํฐ/์ดํฐ๋ ์ดํฐ๋ฅผ ๋ฐ์ ์๋ต ๋ณธ๋ฌธ์ ์คํธ๋ฆฌ๋ฐ ํฉ๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
+
+#### ํ์ผ๊ณผ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ `StreamingResponse`
+
+ํ์ผ๊ณผ ๊ฐ์ ๊ฐ์ฒด(์: `open()`์ผ๋ก ๋ฐํ๋ ๊ฐ์ฒด)๊ฐ ์๋ ๊ฒฝ์ฐ, ํด๋น ํ์ผ๊ณผ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ๋ฐ๋ณต(iterate)ํ๋ ์ ๋๋ ์ดํฐ ํจ์๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
+
+์ด ๋ฐฉ์์ผ๋ก, ํ์ผ ์ ์ฒด๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๋จผ์ ์ฝ์ด๋ค์ผ ํ์ ์์ด, ์ ๋๋ ์ดํฐ ํจ์๋ฅผ `StreamingResponse`์ ์ ๋ฌํ์ฌ ๋ฐํํ ์ ์์ต๋๋ค.
+
+์ด ๋ฐฉ์์ ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง, ๋น๋์ค ์ฒ๋ฆฌ ๋ฑ์ ๋ค์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
+
+1. ์ด๊ฒ์ด ์ ๋๋ ์ดํฐ ํจ์์
๋๋ค. `yield` ๋ฌธ์ ํฌํจํ๊ณ ์์ผ๋ฏ๋ก "์ ๋๋ ์ดํฐ ํจ์"์
๋๋ค.
+2. `with` ๋ธ๋ก์ ์ฌ์ฉํจ์ผ๋ก์จ, ์ ๋๋ ์ดํฐ ํจ์๊ฐ ์๋ฃ๋ ํ ํ์ผ๊ณผ ๊ฐ์ ๊ฐ์ฒด๊ฐ ๋ซํ๋๋ก ํฉ๋๋ค. ์ฆ, ์๋ต ์ ์ก์ด ๋๋ ํ ๋ซํ๋๋ค.
+3. ์ด `yield from`์ ํจ์๊ฐ `file_like`๋ผ๋ ๊ฐ์ฒด๋ฅผ ๋ฐ๋ณต(iterate)ํ๋๋ก ํฉ๋๋ค. ๋ฐ๋ณต๋ ๊ฐ ๋ถ๋ถ์ ์ด ์ ๋๋ ์ดํฐ ํจ์(`iterfile`)์์ ์์ฑ๋ ๊ฒ์ฒ๋ผ `yield` ๋ฉ๋๋ค.
+
+ ์ด๋ ๊ฒ ํ๋ฉด "์์ฑ(generating)" ์์
์ ๋ด๋ถ์ ์ผ๋ก ๋ค๋ฅธ ๋ฌด์ธ๊ฐ์ ์์ํ๋ ์ ๋๋ ์ดํฐ ํจ์๊ฐ ๋ฉ๋๋ค.
+
+ ์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด `with` ๋ธ๋ก ์์์ ํ์ผ์ ์ด ์ ์์ด, ์์
์ด ์๋ฃ๋ ํ ํ์ผ๊ณผ ๊ฐ์ ๊ฐ์ฒด๊ฐ ๋ซํ๋ ๊ฒ์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
+
+/// tip | ํ
+
+์ฌ๊ธฐ์ ํ์ค `open()`์ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ `async`์ `await`๋ฅผ ์ง์ํ์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ๊ฒฝ๋ก ์์
์ ์ผ๋ฐ `def`๋ก ์ ์ธํฉ๋๋ค.
+
+///
+
+### `FileResponse`
+
+ํ์ผ์ ๋น๋๊ธฐ๋ก ์คํธ๋ฆฌ๋ฐํ์ฌ ์๋ตํฉ๋๋ค.
+
+๋ค๋ฅธ ์๋ต ์ ํ๊ณผ๋ ๋ค๋ฅธ ์ธ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค:
+
+* `path` - ์คํธ๋ฆฌ๋ฐํ ํ์ผ์ ๊ฒฝ๋ก.
+* `headers` - ๋์
๋๋ฆฌ ํ์์ ์ฌ์ฉ์ ์ ์ ํค๋.
+* `media_type` - ๋ฏธ๋์ด ํ์
์ ๋ํ๋ด๋ ๋ฌธ์์ด. ์ค์ ๋์ง ์์ ๊ฒฝ์ฐ ํ์ผ ์ด๋ฆ์ด๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ์ฌ ์ถ๋ก ํฉ๋๋ค.
+* `filename` - ์ค์ ๋ ๊ฒฝ์ฐ ์๋ต์ `Content-Disposition`์ ํฌํจ๋ฉ๋๋ค.
+
+ํ์ผ ์๋ต์๋ ์ ์ ํ `Content-Length`, `Last-Modified`, ๋ฐ `ETag` ํค๋๊ฐ ํฌํจ๋ฉ๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
+
+๋ํ `response_class` ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค:
+
+{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
+
+์ด ๊ฒฝ์ฐ, ๊ฒฝ๋ก ์์
ํจ์์์ ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ง์ ๋ฐํํ ์ ์์ต๋๋ค.
+
+## ์ฌ์ฉ์ ์ ์ ์๋ต ํด๋์ค
+
+`Response`๋ฅผ ์์๋ฐ์ ์ฌ์ฉ์ ์ ์ ์๋ต ํด๋์ค๋ฅผ ์์ฑํ๊ณ ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+์๋ฅผ ๋ค์ด, ํฌํจ๋ `ORJSONResponse` ํด๋์ค์์ ์ฌ์ฉ๋์ง ์๋ ์ค์ ์ผ๋ก
orjson์ ์ฌ์ฉํ๊ณ ์ถ๋ค๊ณ ๊ฐ์ ํด๋ด
์๋ค.
+
+๋ง์ฝ ๋ค์ฌ์ฐ๊ธฐ ๋ฐ ํฌ๋งท๋ JSON์ ๋ฐํํ๊ณ ์ถ๋ค๋ฉด, `orjson.OPT_INDENT_2` ์ต์
์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+`CustomORJSONResponse`๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์ ํต์ฌ์ `Response.render(content)` ๋ฉ์๋๋ฅผ ์์ฑํ์ฌ ๋ด์ฉ์ `bytes`๋ก ๋ฐํํ๋ ๊ฒ์
๋๋ค:
+
+{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
+
+์ด์ ๋ค์ ๋์ :
+
+```json
+{"message": "Hello World"}
+```
+
+์ด ์๋ต์ ์ด๋ ๊ฒ ๋ฐํ๋ฉ๋๋ค:
+
+```json
+{
+ "message": "Hello World"
+}
+```
+
+๋ฌผ๋ก JSON ํฌ๋งทํ
๋ณด๋ค ๋ ์ ์ฉํ๊ฒ ํ์ฉํ ๋ฐฉ๋ฒ์ ์ฐพ์ ์ ์์ ๊ฒ์
๋๋ค. ๐
+
+## ๊ธฐ๋ณธ ์๋ต ํด๋์ค
+
+**FastAPI** ํด๋์ค ๊ฐ์ฒด ๋๋ `APIRouter`๋ฅผ ์์ฑํ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ ์๋ต ํด๋์ค๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค.
+
+์ด๋ฅผ ์ ์ํ๋ ๋งค๊ฐ๋ณ์๋ `default_response_class`์
๋๋ค.
+
+์๋ ์์ ์์ **FastAPI**๋ ๋ชจ๋ ๊ฒฝ๋ก ์์
์์ ๊ธฐ๋ณธ์ ์ผ๋ก `JSONResponse` ๋์ `ORJSONResponse`๋ฅผ ์ฌ์ฉํฉ๋๋ค.
+
+{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
+
+/// tip | ํ
+
+์ฌ์ ํ ์ด์ ์ฒ๋ผ *๊ฒฝ๋ก ์์
*์์ `response_class`๋ฅผ ์ฌ์ ์ํ ์ ์์ต๋๋ค.
+
+///
+
+## ์ถ๊ฐ ๋ฌธ์ํ
+
+OpenAPI์์ `responses`๋ฅผ ์ฌ์ฉํ์ฌ ๋ฏธ๋์ด ํ์
๋ฐ ๊ธฐํ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ์ธํ ์๋ ์์ต๋๋ค: [OpenAPI์์ ์ถ๊ฐ ์๋ต](additional-responses.md){.internal-link target=_blank}.
diff --git a/docs/ko/docs/help-fastapi.md b/docs/ko/docs/help-fastapi.md
index 932952b4a..06435d4bb 100644
--- a/docs/ko/docs/help-fastapi.md
+++ b/docs/ko/docs/help-fastapi.md
@@ -1,162 +1,269 @@
-* # FastAPI ์ง์ - ๋์๋ง ๋ฐ๊ธฐ
+# FastAPI ์ง์ - ๋์ ๋ฐ๊ธฐ
- **FastAPI** ๊ฐ ๋ง์์ ๋์๋์?
+**FastAPI** ๊ฐ ๋ง์์ ๋์๋์?
- FastAPI, ๋ค๋ฅธ ์ฌ์ฉ์, ๊ฐ๋ฐ์๋ฅผ ์์ํ๊ณ ์ถ์ผ์ ๊ฐ์?
+FastAPI, ๋ค๋ฅธ ์ฌ์ฉ์, ๊ฐ๋ฐ์๋ฅผ ์์ํ๊ณ ์ถ์ผ์ ๊ฐ์?
- ํน์ **FastAPI** ์ ๋ํด ๋์์ด ํ์ํ์ ๊ฐ์?
+ํน์ **FastAPI** ์ ๋ํด ๋์์ด ํ์ํ์ ๊ฐ์?
- ์์ฃผ ๊ฐ๋จํ๊ฒ ์์ํ ์ ์์ต๋๋ค (๋ช ๋ฒ์ ํด๋ฆญ๋ง์ผ๋ก).
+์์ฃผ ๊ฐ๋จํ๊ฒ ์์ํ ์ ์์ต๋๋ค (๋ช ๋ฒ์ ํด๋ฆญ๋ง์ผ๋ก).
- ๋ํ ๋์์ ๋ฐ์ ์ ์๋ ๋ฐฉ๋ฒ๋ ๋ช ๊ฐ์ง ์์ต๋๋ค.
+๋ํ ๋์์ ๋ฐ์ ์ ์๋ ๋ฐฉ๋ฒ๋ ๋ช ๊ฐ์ง ์์ต๋๋ค.
- ## ๋ด์ค๋ ํฐ ๊ตฌ๋
+## ๋ด์ค๋ ํฐ ๊ตฌ๋
- [**FastAPI์ ์น๊ตฌ** ๋ด์ค๋ ํฐ](https://github.com/fastapi/fastapi/blob/master/newsletter)๋ฅผ ๊ตฌ๋
ํ์ฌ ์ต์ ์ ๋ณด๋ฅผ ์ ์งํ ์ ์์ต๋๋ค{.internal-link target=_blank}:
+[**FastAPI and friends** ๋ด์ค๋ ํฐ](newsletter.md){.internal-link target=\_blank}๋ฅผ ๊ตฌ๋
ํ์ฌ ์ต์ ์ ๋ณด๋ฅผ ์ ์งํ ์ ์์ต๋๋ค:
- - FastAPI ์ ๊ทธ ์น๊ตฌ๋ค์ ๋ํ ๋ด์ค ๐
- - ๊ฐ์ด๋ ๐
- - ํน์ง โจ
- - ํ๊ธฐ์ ์ธ ๋ณํ ๐จ
- - ํ๊ณผ ์๋ น โ
+* FastAPI and friends์ ๋ํ ๋ด์ค ๐
+* ๊ฐ์ด๋ ๐
+* ๊ธฐ๋ฅ โจ
+* ํ๊ธฐ์ ์ธ ๋ณํ ๐จ
+* ํ๊ณผ ์๋ น โ
- ## ํธ์ํฐ์์ FastAPI ํ๋ก์ฐํ๊ธฐ
+## ํธ์ํฐ์์ FastAPI ํ๋ก์ฐํ๊ธฐ
- [Follow @fastapi on **Twitter**](https://twitter.com/fastapi) ๋ฅผ ํ๋ก์ฐํ์ฌ **FastAPI** ์ ๋ํ ์ต์ ๋ด์ค๋ฅผ ์ป์ ์ ์์ต๋๋ค. ๐ฆ
+
**Twitter**์ @fastapi๋ฅผ ํ๋ก์ฐํ์ฌ **FastAPI** ์ ๋ํ ์ต์ ๋ด์ค๋ฅผ ์ป์ ์ ์์ต๋๋ค. ๐ฆ
- ## Star **FastAPI** in GitHub
+## Star **FastAPI** in GitHub
- GitHub์์ FastAPI์ "star"๋ฅผ ๋ถ์ผ ์ ์์ต๋๋ค(์ค๋ฅธ์ชฝ ์๋จ์ star ๋ฒํผ์ ํด๋ฆญ): https://github.com/fastapi/fastapi. โญ๏ธ
+GitHub์์ FastAPI์ "star"๋ฅผ ๋ถ์ผ ์ ์์ต๋๋ค (์ค๋ฅธ์ชฝ ์๋จ์ star ๋ฒํผ์ ํด๋ฆญ):
https://github.com/fastapi/fastapi. โญ๏ธ
- ์คํ๋ฅผ ๋๋ฆผ์ผ๋ก์จ, ๋ค๋ฅธ ์ฌ์ฉ์๋ค์ด ์ข ๋ ์ฝ๊ฒ ์ฐพ์ ์ ์๊ณ , ๋ง์ ์ฌ๋๋ค์๊ฒ ์ ์ฉํ ๊ฒ์์ ๋ํ๋ผ ์ ์์ต๋๋ค.
+์คํ๋ฅผ ๋๋ฆผ์ผ๋ก์จ, ๋ค๋ฅธ ์ฌ์ฉ์๋ค์ด ์ข ๋ ์ฝ๊ฒ ์ฐพ์ ์ ์๊ณ , ๋ง์ ์ฌ๋๋ค์๊ฒ ์ ์ฉํ ๊ฒ์์ ๋ํ๋ผ ์ ์์ต๋๋ค.
- ## GitHub ์ ์ฅ์์์ ๋ฆด๋ฆฌ์ฆ ํ์ธ
+## GitHub ์ ์ฅ์์์ ๋ฆด๋ฆฌ์ฆ ํ์ธ
- GitHub์์ FastAPI๋ฅผ "watch"ํ ์ ์์ต๋๋ค (์ค๋ฅธ์ชฝ ์๋จ watch ๋ฒํผ์ ํด๋ฆญ): https://github.com/fastapi/fastapi. ๐
+GitHub์์ FastAPI๋ฅผ "watch"ํ ์ ์์ต๋๋ค (์ค๋ฅธ์ชฝ ์๋จ watch ๋ฒํผ์ ํด๋ฆญ):
https://github.com/fastapi/fastapi. ๐
- ์ฌ๊ธฐ์ "Releases only"์ ์ ํํ ์ ์์ต๋๋ค.
+์ฌ๊ธฐ์ "Releases only"์ ์ ํํ ์ ์์ต๋๋ค.
- ์ด๋ ๊ฒํ๋ฉด, **FastAPI** ์ ๋ฒ๊ทธ ์์ ๋ฐ ์๋ก์ด ๊ธฐ๋ฅ์ ๊ตฌํ ๋ฑ์ ์๋ก์ด ์๋ฃ (์ต์ ๋ฒ์ )์ด ์์ ๋๋ง๋ค (์ด๋ฉ์ผ) ํต์ง๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
+์ด๋ ๊ฒํ๋ฉด, **FastAPI** ์ ๋ฒ๊ทธ ์์ ๋ฐ ์๋ก์ด ๊ธฐ๋ฅ์ ๊ตฌํ ๋ฑ์ ์๋ก์ด ์๋ฃ (์ต์ ๋ฒ์ )์ด ์์ ๋๋ง๋ค (์ด๋ฉ์ผ) ํต์ง๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
- ## ๊ฐ๋ฐ์์์ ์ฐ๊ฒฐ
+## ๊ฐ๋ฐ์์์ ์ฐ๊ฒฐ
- ๊ฐ๋ฐ์์ธ [me (Sebastiรกn Ramรญrez / `tiangolo`)](https://tiangolo.com/) ์ ์ฐ๋ฝ์ ์ทจํ ์ ์์ต๋๋ค.
+
๊ฐ๋ฐ์(Sebastiรกn Ramรญrez / `tiangolo`)์ ์ฐ๋ฝ์ ์ทจํ ์ ์์ต๋๋ค.
- ์ฌ๋ฌ๋ถ์ ํ ์ ์์ต๋๋ค:
+์ฌ๋ฌ๋ถ์ ํ ์ ์์ต๋๋ค:
- - [**GitHub**์์ ํ๋ก์ฐํ๊ธฐ](https://github.com/tiangolo).
- - ๋น์ ์๊ฒ ๋์์ด ๋ ์ ์ ๋ค๋ฅธ ์คํ์์ค ํ๋ก์ ํธ๋ฅผ ํ์ธํ์ญ์์ค.
- - ์๋ก์ด ์คํ์์ค ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์์ ๋ ํ์ธํ๋ ค๋ฉด ํ๋ก์ฐ ํ์ญ์์ค.
+*
**GitHub**์์ ํ๋ก์ฐํ๊ธฐ..
+ * ๋น์ ์๊ฒ ๋์์ด ๋ ์ ์ ๋ค๋ฅธ ์คํ์์ค ํ๋ก์ ํธ๋ฅผ ํ์ธํ์ญ์์ค.
+ * ์๋ก์ด ์คํ์์ค ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์์ ๋ ํ์ธํ๋ ค๋ฉด ํ๋ก์ฐ ํ์ญ์์ค.
+*
**Twitter** ๋๋
Mastodon์์ ํ๋ก์ฐํ๊ธฐ.
+ * FastAPI์ ์ฌ์ฉ ์ฉ๋๋ฅผ ์๋ ค์ฃผ์ธ์ (๊ทธ๊ฒ์ ๋ฃ๋ ๊ฒ์ ์ข์ํฉ๋๋ค).
+ * ๋ฐํ๋ ์๋ก์ด ํด ์ถ์ ์์์ ๋ฐ์๋ณด์ญ์์ค.
+ *
**Twitter**์ @fastapi๋ฅผ ํ๋ก์ฐ (๋ณ๋ ๊ณ์ ์์) ํ ์ ์์ต๋๋ค.
+*
**LinkedIn**์์ ํ๋ก์ฐํ๊ธฐ..
+ * ์๋ก์ด ํด์ ๋ฐํ๋ ์ถ์ ์์์ ๋ฐ์๋ณด์ญ์์ค. (๋จ, Twitter๋ฅผ ๋ ์์ฃผ ์ฌ์ฉํฉ๋๋ค ๐คทโโ).
+*
**Dev.to** ๋๋
**Medium**์์ ์ ๊ฐ ์์ฑํ ๋ด์ฉ์ ์ฝ์ด ๋ณด์ญ์์ค (๋๋ ํ๋ก์ฐ).
+ * ๋ค๋ฅธ ๊ธฐ์ฌ๋ ์์ด๋์ด๋ค์ ์ฝ๊ณ , ์ ๊ฐ ๋ง๋ค์ด์๋ ํด์ ๋ํด์๋ ์ฝ์ผ์ญ์์ค.
+ * ์๋ก์ด ๊ธฐ์ฌ๋ฅผ ์ฝ๊ธฐ ์ํด ํ๋ก์ฐ ํ์ญ์์ค.
- - [**Twitter**์์ ํ๋ก์ฐํ๊ธฐ](https://twitter.com/tiangolo).
- - FastAPI์ ์ฌ์ฉ ์ฉ๋๋ฅผ ์๋ ค์ฃผ์ธ์ (๊ทธ๊ฒ์ ๋ฃ๋ ๊ฒ์ ์ข์ํฉ๋๋ค).
- - ๋ฐํ ๋๋ ์๋ก์ด ํด ์ถ์ํ ๋ ๋ค์ผ์ญ์์ค.
- - [follow @fastapi on Twitter](https://twitter.com/fastapi) (๋ณ๋ ๊ณ์ ์์) ํ ์ ์์ต๋๋ค.
+## **FastAPI**์ ๋ํ ํธ์
- - [**Linkedin**์์์ ์ฐ๊ฒฐ](https://www.linkedin.com/in/tiangolo/).
- - ์๋ก์ด ํด์ ๋ฐํ๋ ๋ฆด๋ฆฌ์ค๋ฅผ ๋ค์ ์ ์์ต๋๋ค (๋จ, Twitter๋ฅผ ๋ ์์ฃผ ์ฌ์ฉํฉ๋๋ค ๐คทโโ).
+
**FastAPI**์ ๋ํด ํธ์ ํ๊ณ FastAPI๊ฐ ๋ง์์ ๋๋ ์ด์ ๋ฅผ ์๋ ค์ฃผ์ธ์. ๐
- - [**Dev.to**](https://dev.to/tiangolo) ๋๋ [**Medium**](https://medium.com/@tiangolo)์์ ์ ๊ฐ ์์ฑํ ๋ด์ฉ์ ์ฝ์ด ๋ณด์ญ์์ค(๋๋ ํ๋ก์ฐ).
- - ๋ค๋ฅธ ๊ธฐ์ฌ๋ ์์ด๋์ด๋ค์ ์ฝ๊ณ , ์ ๊ฐ ๋ง๋ค์ด์๋ ํด์ ๋ํด์๋ ์ฝ์ผ์ญ์์ค.
- - ์๋ก์ด ๊ธฐ์ฌ๋ฅผ ์ฝ๊ธฐ ์ํด ํ๋ก์ฐ ํ์ญ์์ค.
+**FastAPI**๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋๊ณ ์๋์ง, ์ด๋ค ์ ์ด ๋ง์์ ๋ค์๋์ง, ์ด๋ค ํ๋ก์ ํธ/ํ์ฌ์์ ์ฌ์ฉํ๊ณ ์๋์ง ๋ฑ์ ๋ํด ๋ฃ๊ณ ์ถ์ต๋๋ค.
- ## **FastAPI**์ ๋ํ ํธ์
+## FastAPI์ ํฌํํ๊ธฐ
- [**FastAPI**์ ๋ํด ํธ์](https://twitter.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi) ํ๊ณ FastAPI๊ฐ ๋ง์์ ๋๋ ์ด์ ๋ฅผ ์๋ ค์ฃผ์ธ์. ๐
+*
Slant์์ **FastAPI** ์ ๋ํด ํฌํํ์ญ์์ค.
+*
AlternativeTo์์ **FastAPI** ์ ๋ํด ํฌํํ์ญ์์ค.
+*
StackShare์์ **FastAPI** ์ ๋ํด ํฌํํ์ญ์์ค.
- **FastAPI**๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋๊ณ ์๋์ง, ์ด๋ค ์ ์ด ๋ง์์ ๋ค์๋์ง, ์ด๋ค ํ๋ก์ ํธ/ํ์ฌ์์ ์ฌ์ฉํ๊ณ ์๋์ง ๋ฑ์ ๋ํด ๋ฃ๊ณ ์ถ์ต๋๋ค.
+## GitHub์ ์ด์๋ก ๋ค๋ฅธ์ฌ๋ ๋๊ธฐ
- ## FastAPI์ ํฌํํ๊ธฐ
+๋ค๋ฅธ ์ฌ๋๋ค์ ์ง๋ฌธ์ ๋์์ ์ค ์ ์์ต๋๋ค:
- - [Slant์์ **FastAPI** ์ ๋ํด ํฌํํ์ญ์์ค](https://www.slant.co/options/34241/~fastapi-review).
- - [AlternativeTo**FastAPI** ์ ๋ํด ํฌํํ์ญ์์ค](https://alternativeto.net/software/fastapi/).
+*
GitHub ๋์ค์ปค์
+*
GitHub ์ด์
- ## GitHub์ ์ด์๋ก ๋ค๋ฅธ์ฌ๋ ๋๊ธฐ
+๋ง์ ๊ฒฝ์ฐ, ์ฌ๋ฌ๋ถ์ ์ด๋ฏธ ๊ทธ ์ง๋ฌธ์ ๋ํ ๋ต์ ์๊ณ ์์ ์๋ ์์ต๋๋ค. ๐ค
- [์กด์ฌํ๋ ์ด์](https://github.com/fastapi/fastapi/issues)๋ฅผ ํ์ธํ๊ณ ๊ทธ๊ฒ์ ์๋ํ๊ณ ๋์์ค ์ ์์ต๋๋ค. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ด๋ฏธ ๋ต์ ์๊ณ ์๋ ์ง๋ฌธ์
๋๋ค. ๐ค
+๋ง์ฝ ๋ง์ ์ฌ๋๋ค์ ๋ฌธ์ ๋ฅผ ๋์์ค๋ค๋ฉด, ๊ณต์์ ์ธ [FastAPI ์ ๋ฌธ๊ฐ](fastapi-people.md#fastapi-experts){.internal-link target=\_blank} ๊ฐ ๋ ๊ฒ์
๋๋ค. ๐
- ๋ง์ ์ฌ๋๋ค์ ๋ฌธ์ ๋ฅผ ๋์์ค๋ค๋ฉด, ๊ณต์์ ์ธ [FastAPI ์ ๋ฌธ๊ฐ](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) ๊ฐ ๋ ์ ์์ต๋๋ค{.internal-link target=_blank}. ๐
+๊ฐ์ฅ ์ค์ํ ์ ์: ์น์ ํ๋ ค๊ณ ๋
ธ๋ ฅํ๋ ๊ฒ์
๋๋ค. ์ฌ๋๋ค์ ์ข์ ๊ฐ์ ์๊ณ ์ค๋ฉฐ, ๋ง์ ๊ฒฝ์ฐ ์ต์ ์ ๋ฐฉ์์ผ๋ก ์ง๋ฌธํ์ง ์์ ์๋ ์์ต๋๋ค. ํ์ง๋ง ์ต๋ํ ์น์ ํ๊ฒ ๋ํ๋ ค๊ณ ๋
ธ๋ ฅํ์ธ์. ๐ค
- ## GitHub ์ ์ฅ์ ๋ณด๊ธฐ
+**FastAPI** ์ปค๋ฎค๋ํฐ์ ๋ชฉํ๋ ์น์ ํ๊ณ ํ์ํ๋ ๊ฒ์
๋๋ค. ๋์์, ๊ดด๋กญํ์ด๋ ๋ฌด๋กํ ํ๋์ ๋ฐ์๋ค์ด์ง ๋ง์ธ์. ์ฐ๋ฆฌ๋ ์๋ก๋ฅผ ๋๋ด์ผ ํฉ๋๋ค.
- GitHub์์ FastAPI๋ฅผ "watch"ํ ์ ์์ต๋๋ค (์ค๋ฅธ์ชฝ ์๋จ watch ๋ฒํผ์ ํด๋ฆญ): https://github.com/fastapi/fastapi. ๐
+---
- "Releases only" ๋์ "Watching"์ ์ ํํ๋ฉด ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์๋ก์ด issue๋ฅผ ์์ฑํ ๋ ์๋ฆผ์ด ์์ ๋ฉ๋๋ค.
+๋ค๋ฅธ ์ฌ๋๋ค์ ์ง๋ฌธ (๋์ค์ปค์
๋๋ ์ด์์์) ํด๊ฒฐ์ ๋์ธ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๊ทธ๋ฐ ๋ค์ ์ด๋ฐ issues๋ฅผ ํด๊ฒฐ ํ ์ ์๋๋ก ๋์์ ์ค ์ ์์ต๋๋ค.
+### ์ง๋ฌธ ์ดํดํ๊ธฐ
- ## ์ด์ ์์ฑํ๊ธฐ
+* ์ง๋ฌธํ๋ ์ฌ๋์ด ๊ฐ์ง **๋ชฉ์ **๊ณผ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์ดํดํ ์ ์๋์ง ํ์ธํ์ธ์.
- GitHub ์ ์ฅ์์ [์๋ก์ด ์ด์ ์์ฑ](https://github.com/fastapi/fastapi/issues/new/choose) ์ ํ ์ ์์ต๋๋ค, ์๋ฅผ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
+* ์ง๋ฌธ (๋๋ถ๋ถ์ ์ง๋ฌธ์
๋๋ค)์ด **๋ช
ํ**ํ์ง ํ์ธํ์ธ์.
- - **์ง๋ฌธ**์ ํ๊ฑฐ๋ **๋ฌธ์ **์ ๋ํด ์ง๋ฌธํฉ๋๋ค.
- - ์๋ก์ด **๊ธฐ๋ฅ**์ ์ ์ ํฉ๋๋ค.
+* ๋ง์ ๊ฒฝ์ฐ, ์ฌ์ฉ์๊ฐ ๊ฐ์ ํ ํด๊ฒฐ์ฑ
์ ๋ํ ์ง๋ฌธ์ ํ์ง๋ง, ๋ **์ข์** ํด๊ฒฐ์ฑ
์ด ์์ ์ ์์ต๋๋ค. ๋ฌธ์ ์ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ๋ ์ ์ดํดํ๋ฉด ๋ ๋์ **๋์์ ์ธ ํด๊ฒฐ์ฑ
**์ ์ ์ํ ์ ์์ต๋๋ค.
- **์ฐธ๊ณ **: ๋ง์ฝ ์ด์๋ฅผ ์์ฑํ๋ค๋ฉด, ์ ๋ ์ฌ๋ฌ๋ถ์๊ฒ ๋ค๋ฅธ ์ฌ๋๋ค์ ๋์๋ฌ๋ผ๊ณ ๋ถํํ ๊ฒ์
๋๋ค. ๐
+* ์ง๋ฌธ์ ์ดํดํ ์ ์๋ค๋ฉด, ๋ **์์ธํ ์ ๋ณด**๋ฅผ ์์ฒญํ์ธ์.
- ## Pull Request๋ฅผ ๋ง๋์ญ์์ค
+### ๋ฌธ์ ์ฌํํ๊ธฐ
- Pull Requests๋ฅผ ์ด์ฉํ์ฌ ์์ค์ฝ๋์ [์ปจํธ๋ฆฌ๋ทฐํธ](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/contributing.md){.internal-link target=_blank} ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
+๋๋ถ๋ถ์ ๊ฒฝ์ฐ, ์ง๋ฌธ์ ์ง๋ฌธ์์ **์๋ณธ ์ฝ๋**์ ๊ด๋ จ์ด ์์ต๋๋ค.
- - ๋ฌธ์์์ ์ฐพ์ ์คํ๋ฅผ ์์ ํ ๋.
+๋ง์ ๊ฒฝ์ฐ, ์ฝ๋์ ์ผ๋ถ๋ง ๋ณต์ฌํด์ ์ฌ๋ฆฌ์ง๋ง, ๊ทธ๊ฒ๋ง์ผ๋ก๋ **๋ฌธ์ ๋ฅผ ์ฌํ**ํ๊ธฐ์ ์ถฉ๋ถํ์ง ์์ต๋๋ค.
- - FastAPI๋ฅผ [ํธ์งํ์ฌ](https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml) ์์ฑํ๊ฑฐ๋ ์ฐพ์ ๋ฌธ์, ๋น๋์ค ๋๋ ํ์บ์คํธ๋ฅผ ๊ณต์ ํ ๋.
+* ์ง๋ฌธ์์๊ฒ
์ต์ํ์ ์ฌํ ๊ฐ๋ฅํ ์์ ๋ฅผ ์ ๊ณตํด๋ฌ๋ผ๊ณ ์์ฒญํ์ธ์. ์ด๋ ๊ฒ ํ๋ฉด ์ฝ๋๋ฅผ **๋ณต์ฌ-๋ถ์ฌ๋ฃ๊ธฐ**ํ์ฌ ์ง์ ์คํํ๊ณ , ๋์ผํ ์ค๋ฅ๋ ๋์์ ํ์ธํ๊ฑฐ๋ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ๋ ์ ์ดํดํ ์ ์์ต๋๋ค.
- - ํด๋น ์น์
์ ์์ ๋ถ๋ถ์ ๋งํฌ๋ฅผ ์ถ๊ฐํ๋์ง ํ์ธํ์ญ์์ค.
+* ๋๊ทธ๋ฌ์ด ๋ง์์ด ๋ ๋ค๋ฉด, ๋ฌธ์ ์ค๋ช
๋ง์ ๊ธฐ๋ฐ์ผ๋ก ์ง์ **์์ ๋ฅผ ๋ง๋ค์ด**๋ณผ ์๋ ์์ต๋๋ค. ํ์ง๋ง, ์ด๋ ์๊ฐ์ด ๋ง์ด ๊ฑธ๋ฆด ์ ์์ผ๋ฏ๋ก, ๋จผ์ ์ง๋ฌธ์ ๋ช
ํํ ํด๋ฌ๋ผ๊ณ ์์ฒญํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- - ๋น์ ์ ์ธ์ด๋ก [๋ฌธ์ ๋ฒ์ญํ๋๋ฐ](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/contributing.md#translations){.internal-link target=_blank} ๊ธฐ์ฌํ ๋.
+### ํด๊ฒฐ์ฑ
์ ์ํ๊ธฐ
- - ๋ํ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ๋ง๋ ๋ฒ์ญ์ ๊ฒํ ํ๋๋ฐ ๋์์ ์ค ์๋ ์์ต๋๋ค.
+* ์ง๋ฌธ์ ์ถฉ๋ถํ ์ดํดํ ํ์๋ ๊ฐ๋ฅํ **๋ต๋ณ**์ ์ ๊ณตํ ์ ์์ต๋๋ค.
- - ์๋ก์ด ๋ฌธ์์ ์น์
์ ์ ์ํ ๋.
+* ๋ง์ ๊ฒฝ์ฐ, ์ง๋ฌธ์์ **๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ๋ ์ฌ์ฉ ์ฌ๋ก**๋ฅผ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๊ทธ๋ค์ด ์๋ํ๋ ๋ฐฉ๋ฒ๋ณด๋ค ๋ ๋์ ํด๊ฒฐ์ฑ
์ด ์์ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
- - ๊ธฐ์กด ๋ฌธ์ /๋ฒ๊ทธ๋ฅผ ์์ ํ ๋.
+### ํด๊ฒฐ ์์ฒญํ๊ธฐ
- - ์๋ก์ด feature๋ฅผ ์ถ๊ฐํ ๋.
+์ง๋ฌธ์๊ฐ ๋ต๋ณ์ ํ์ธํ๊ณ ๋๋ฉด, ๋น์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ์ถํํฉ๋๋ค, **๋น์ ์ ์์
์
๋๋ค**! ๐ฆธ
- ## ์ฑํ
์ ์ฐธ์ฌํ์ญ์์ค
+* ์ด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค๋ฉด, ์ง๋ฌธ์์๊ฒ ๋ค์์ ์์ฒญํ ์ ์์ต๋๋ค.
- ๐ฅ [๋์ค์ฝ๋ ์ฑํ
์๋ฒ](https://discord.gg/VQjSZaeJmf) ๐ฅ ์ ๊ฐ์
ํ๊ณ FastAPI ์ปค๋ฎค๋ํฐ์์ ๋ค๋ฅธ ์ฌ๋๋ค๊ณผ ์ด์ธ๋ฆฌ์ธ์.
+ * GitHub ๋์ค์ปค์
์์: ๋๊ธ์ **๋ต๋ณ**์ผ๋ก ํ์ํ๋๋ก ์์ฒญํ์ธ์.
+ * GitHub ์ด์์์: ์ด์๋ฅผ **๋ซ์๋ฌ๋ผ๊ณ ** ์์ฒญํ์ธ์.
- /// tip
+## GitHub ์ ์ฅ์ ๋ณด๊ธฐ
- ์ง๋ฌธ์ด ์๋ ๊ฒฝ์ฐ, [GitHub ์ด์ ](https://github.com/fastapi/fastapi/issues/new/choose) ์์ ์ง๋ฌธํ์ญ์์ค, [FastAPI ์ ๋ฌธ๊ฐ](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) ์ ๋์์ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค{.internal-link target=_blank} .
+GitHub์์ FastAPI๋ฅผ "watch"ํ ์ ์์ต๋๋ค (์ค๋ฅธ์ชฝ ์๋จ watch ๋ฒํผ์ ํด๋ฆญ):
https://github.com/fastapi/fastapi. ๐
- ///
+"Releases only" ๋์ "Watching"์ ์ ํํ๋ฉด, ์๋ก์ด ์ด์๋ ์ง๋ฌธ์ด ์์ฑ๋ ๋ ์๋ฆผ์ ๋ฐ์ ์ ์์ต๋๋ค. ๋ํ, ํน์ ํ๊ฒ ์๋ก์ด ์ด์, ๋์ค์ปค์
, PR ๋ฑ๋ง ์๋ฆผ ๋ฐ๋๋ก ์ค์ ํ ์๋ ์์ต๋๋ค.
- ```
- ๋ค๋ฅธ ์ผ๋ฐ์ ์ธ ๋ํ์์๋ง ์ฑํ
์ ์ฌ์ฉํ์ญ์์ค.
- ```
+๊ทธ๋ฐ ๋ค์ ์ด๋ฐ ์ด์๋ค์ ํด๊ฒฐ ํ ์ ์๋๋ก ๋์์ ์ค ์ ์์ต๋๋ค.
- ๊ธฐ์กด [์งํฐ ์ฑํ
](https://gitter.im/fastapi/fastapi) ์ด ์์ง๋ง ์ฑ๋๊ณผ ๊ณ ๊ธ๊ธฐ๋ฅ์ด ์์ด์ ๋ํ๋ฅผ ํ๊ธฐ๊ฐ ์กฐ๊ธ ์ด๋ ต๊ธฐ ๋๋ฌธ์ ์ง๊ธ์ ๋์ค์ฝ๋๊ฐ ๊ถ์ฅ๋๋ ์์คํ
์
๋๋ค.
+## ์ด์ ์์ฑํ๊ธฐ
- ### ์ง๋ฌธ์ ์ํด ์ฑํ
์ ์ฌ์ฉํ์ง ๋ง์ญ์์ค
+GitHub ์ ์ฅ์์
์๋ก์ด ์ด์ ์์ฑ์ ํ ์ ์์ต๋๋ค, ์๋ฅผ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ฑํ
์ ๋ ๋ง์ "์์ ๋ก์ด ๋ํ"๋ฅผ ํ์ฉํ๊ธฐ ๋๋ฌธ์, ๋๋ฌด ์ผ๋ฐ์ ์ธ ์ง๋ฌธ์ด๋ ๋๋ตํ๊ธฐ ์ด๋ ค์ด ์ง๋ฌธ์ ์ฝ๊ฒ ์ง๋ฌธ์ ํ ์ ์์ผ๋ฏ๋ก, ๋ต๋ณ์ ๋ฐ์ง ๋ชปํ ์ ์์ต๋๋ค.
+* **์ง๋ฌธ**์ ํ๊ฑฐ๋ **๋ฌธ์ **์ ๋ํด ์ง๋ฌธํฉ๋๋ค.
+* ์๋ก์ด **๊ธฐ๋ฅ**์ ์ ์ ํฉ๋๋ค.
- GitHub ์ด์์์์ ํ
ํ๋ฆฟ์ ์ฌ๋ฐ๋ฅธ ์ง๋ฌธ์ ์์ฑํ๋๋ก ์๋ดํ์ฌ ๋ ์ฝ๊ฒ ์ข์ ๋ต๋ณ์ ์ป๊ฑฐ๋ ์ง๋ฌธํ๊ธฐ ์ ์ ์ค์ค๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์๋ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ GitHub์์๋ ์๊ฐ์ด ์กฐ๊ธ ๊ฑธ๋ฆฌ๋๋ผ๋ ํญ์ ๋ชจ๋ ๊ฒ์ ๋ตํ ์ ์์ต๋๋ค. ์ฑํ
์์คํ
์์๋ ๊ฐ์ธ์ ์ผ๋ก ๊ทธ๋ ๊ฒ ํ ์ ์์ต๋๋ค. ๐
+**์ฐธ๊ณ **: ๋ง์ฝ ์ด์๋ฅผ ์์ฑํ๋ค๋ฉด, ์ ๋ ์ฌ๋ฌ๋ถ์๊ฒ ๋ค๋ฅธ ์ฌ๋๋ค์ ๋์๋ฌ๋ผ๊ณ ๋ถํํ ๊ฒ์
๋๋ค. ๐
- ์ฑํ
์์คํ
์์์ ๋ํ ๋ํ GitHub์์ ์ฒ๋ผ ์ฝ๊ฒ ๊ฒ์ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ํ ์ค์ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ด ์์ค๋ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ GitHub ์ด์์ ์๋ ๊ฒ๋ง [FastAPI ์ ๋ฌธ๊ฐ](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts)๊ฐ ๋๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ๋๋ฏ๋ก{.internal-link target=_blank} , GitHub ์ด์์์ ๋ ๋ง์ ๊ด์ฌ์ ๋ฐ์ ๊ฒ์
๋๋ค.
+## Pull Requests ๋ฆฌ๋ทฐํ๊ธฐ
- ๋ฐ๋ฉด, ์ฑํ
์์คํ
์๋ ์์ฒ ๋ช
์ ์ฌ์ฉ์๊ฐ ์๊ธฐ ๋๋ฌธ์, ๊ฑฐ์ ํญ์ ๋ํ ์๋๋ฅผ ์ฐพ์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ๐
+๋ค๋ฅธ ์ฌ๋๋ค์ pull request๋ฅผ ๋ฆฌ๋ทฐํ๋ ๋ฐ ๋์์ ์ค ์ ์์ต๋๋ค.
- ## ๊ฐ๋ฐ์ ์คํฐ์๊ฐ ๋์ญ์์ค
+๋ค์ ํ๋ฒ ๋งํ์ง๋ง, ์ต๋ํ ์น์ ํ๊ฒ ๋ฆฌ๋ทฐํด ์ฃผ์ธ์. ๐ค
- [GitHub ์คํฐ์](https://github.com/sponsors/tiangolo) ๋ฅผ ํตํด ๊ฐ๋ฐ์๋ฅผ ๊ฒฝ์ ์ ์ผ๋ก ์ง์ํ ์ ์์ต๋๋ค.
+---
- ๊ฐ์ฌํ๋ค๋ ๋ง๋ก ์ปคํผ๋ฅผ โ๏ธ ํ์ ์ฌ์ค ์ ์์ต๋๋ค. ๐
+Pull Rrquest๋ฅผ ๋ฆฌ๋ทฐํ ๋ ๊ณ ๋ คํด์ผ ํ ์ฌํญ๊ณผ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋ํ FastAPI์ ์ค๋ฒ ๋๋ ๊ณจ๋ ์คํฐ์๊ฐ ๋ ์ ์์ต๋๋ค. ๐
๐
+### ๋ฌธ์ ์ดํดํ๊ธฐ
- ## FastAPI๋ฅผ ๊ฐํํ๋ ๋๊ตฌ์ ์คํฐ์๊ฐ ๋์ญ์์ค
+* ๋จผ์ , ํด๋น pull request๊ฐ ํด๊ฒฐํ๋ ค๋ **๋ฌธ์ ๋ฅผ ์ดํดํ๋์ง** ํ์ธํ์ธ์. GitHub ๋์ค์ปค์
๋๋ ์ด์์์ ๋ ๊ธด ๋
ผ์๊ฐ ์์์ ์๋ ์์ต๋๋ค.
- ๋ฌธ์์์ ๋ณด์๋ฏ์ด, FastAPI๋ Starlette๊ณผ Pydantic ๋ผ๋ ๊ฑฐ์ธ์ ์ด๊นจ์ ํ๊ณ ์์ต๋๋ค.
+* Pull request๊ฐ ํ์ํ์ง ์์ ๊ฐ๋ฅ์ฑ๋ ์์ต๋๋ค. **๋ค๋ฅธ ๋ฐฉ์**์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค๋ฉด, ๊ทธ ๋ฐฉ๋ฒ์ ์ ์ํ๊ฑฐ๋ ์ง๋ฌธํ ์ ์์ต๋๋ค.
- ๋ค์์ ์คํฐ์๊ฐ ๋ ์ ์์ต๋๋ค
+### ์คํ์ผ์ ๋๋ฌด ์ ๊ฒฝ ์ฐ์ง ์๊ธฐ
- - [Samuel Colvin (Pydantic)](https://github.com/sponsors/samuelcolvin)
- - [Encode (Starlette, Uvicorn)](https://github.com/sponsors/encode)
+* ์ปค๋ฐ ๋ฉ์์ง ์คํ์ผ ๊ฐ์ ๊ฒ์ ๋๋ฌด ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋ฉ๋๋ค. ์ ๋ ์ง์ ์ปค๋ฐ์ ์์ ํ์ฌ squash and merge๋ฅผ ์ํํ ๊ฒ์
๋๋ค.
- ------
+* ์ฝ๋ ์คํ์ผ ๊ท์น๋ ๊ฑฑ์ ํ ํ์ ์์ต๋๋ค. ์ด๋ฏธ ์๋ํ๋ ๋๊ตฌ๋ค์ด ์ด๋ฅผ ๊ฒ์ฌํ๊ณ ์์ต๋๋ค.
- ๊ฐ์ฌํฉ๋๋ค! ๐
+์คํ์ผ์ด๋ ์ผ๊ด์ฑ ๊ด๋ จ ์์ฒญ์ด ํ์ํ ๊ฒฝ์ฐ, ์ ๊ฐ ์ง์ ์์ฒญํ๊ฑฐ๋ ํ์ํ ๋ณ๊ฒฝ ์ฌํญ์ ์ถ๊ฐ ์ปค๋ฐ์ผ๋ก ์์ ํ ๊ฒ์
๋๋ค.
+
+### ์ฝ๋ ํ์ธํ๊ธฐ
+
+* ์ฝ๋๋ฅผ ์ฝ๊ณ , **๋
ผ๋ฆฌ์ ์ผ๋ก ํ๋น**ํ์ง ํ์ธํ ํ ๋ก์ปฌ์์ ์คํํ์ฌ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋์ง ํ์ธํ์ธ์.
+
+* ๊ทธ๋ฐ ๋ค์, ํ์ธํ๋ค๊ณ **๋๊ธ**์ ๋จ๊ฒจ ์ฃผ์ธ์. ๊ทธ๋์ผ ์ ๊ฐ ๊ฒํ ํ์์ ์ ์ ์์ต๋๋ค.
+
+/// info
+
+๋ถํํ๋, ์ ๊ฐ ๋จ์ํ ์ฌ๋ฌ ๊ฐ์ ์น์ธ๋ง์ผ๋ก PR์ ์ ๋ขฐํ ์๋ ์์ต๋๋ค.
+
+3๊ฐ, 5๊ฐ ์ด์์ ์น์ธ์ด ๋ฌ๋ฆฐ PR์ด ์ค์ ๋ก๋ ๊นจ์ ธ ์๊ฑฐ๋, ๋ฒ๊ทธ๊ฐ ์๊ฑฐ๋, ์ฃผ์ฅํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ์ฌ๋ฌ ๋ฒ ์์์ต๋๋ค. ๐
+
+๋ฐ๋ผ์, ์ ๋ง๋ก ์ฝ๋๋ฅผ ์ฝ๊ณ ์คํํ ๋ค, ๋๊ธ๋ก ํ์ธ ๋ด์ฉ์ ๋จ๊ฒจ ์ฃผ๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํฉ๋๋ค. ๐ค
+
+///
+
+* PR์ ๋ ๋จ์ํ๊ฒ ๋ง๋ค ์ ์๋ค๋ฉด ๊ทธ๋ ๊ฒ ์์ฒญํ ์ ์์ง๋ง, ๋๋ฌด ๊น๋ค๋ก์ธ ํ์๋ ์์ต๋๋ค. ์ฃผ๊ด์ ์ธ ๊ฒฌํด๊ฐ ๋ง์ด ์์ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค (๊ทธ๋ฆฌ๊ณ ์ ๋ ์ ๊ฒฌํด๊ฐ ์์ ๊ฑฐ์์ ๐). ๋ฐ๋ผ์ ํต์ฌ์ ์ธ ๋ถ๋ถ์ ์ง์คํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
+
+### ํ
์คํธ
+
+* PR์ **ํ
์คํธ**๊ฐ ํฌํจ๋์ด ์๋์ง ํ์ธํ๋ ๋ฐ ๋์์ ์ฃผ์ธ์.
+
+* PR์ ์ ์ฉํ๊ธฐ ์ ์ ํ
์คํธ๊ฐ **์คํจ**ํ๋์ง ํ์ธํ์ธ์. ๐จ
+
+* PR์ ์ ์ฉํ ํ ํ
์คํธ๊ฐ **ํต๊ณผ**ํ๋์ง ํ์ธํ์ธ์. โ
+
+* ๋ง์ PR์๋ ํ
์คํธ๊ฐ ์์ต๋๋ค. ํ
์คํธ๋ฅผ ์ถ๊ฐํ๋๋ก **์๊ธฐ**์์ผ์ค ์๋ ์๊ณ , ์ง์ ํ
์คํธ๋ฅผ **์ ์**ํ ์๋ ์์ต๋๋ค. ์ด๋ ์๊ฐ์ด ๋ง์ด ์์๋๋ ๋ถ๋ถ ์ค ํ๋์ด๋ฉฐ, ๊ทธ ๋ถ๋ถ์ ๋ง์ด ๋์์ค ์ ์์ต๋๋ค.
+
+* ๊ทธ๋ฆฌ๊ณ ์๋ํ ๋ด์ฉ์ ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์ธ์. ๊ทธ๋ฌ๋ฉด ์ ๊ฐ ํ์ธํ๋ค๋ ๊ฑธ ์ ์ ์์ต๋๋ค. ๐ค
+
+## Pull Request๋ฅผ ๋ง๋์ญ์์ค
+
+Pull Requests๋ฅผ ์ด์ฉํ์ฌ ์์ค์ฝ๋์ [์ปจํธ๋ฆฌ๋ทฐํธ](contributing.md){.internal-link target=\_blank} ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
+
+* ๋ฌธ์์์ ๋ฐ๊ฒฌํ ์คํ๋ฅผ ์์ ํ ๋.
+* FastAPI ๊ด๋ จ ๋ฌธ์, ๋น๋์ค ๋๋ ํ์บ์คํธ๋ฅผ ์์ฑํ๊ฑฐ๋ ๋ฐ๊ฒฌํ์ฌ
์ด ํ์ผ์ ํธ์งํ์ฌ ๊ณต์ ํ ๋.
+ * ํด๋น ์น์
์ ์์ ๋ถ๋ถ์ ๋งํฌ๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
+* ๋น์ ์ ์ธ์ด๋ก [๋ฌธ์ ๋ฒ์ญํ๋๋ฐ](contributing.md#translations){.internal-link target=\_blank} ๊ธฐ์ฌํ ๋.
+ * ๋ค๋ฅธ ์ฌ๋์ด ์์ฑํ ๋ฒ์ญ์ ๊ฒํ ํ๋ ๊ฒ๋ ๋์ธ ์ ์์ต๋๋ค.
+* ์๋ก์ด ๋ฌธ์์ ์น์
์ ์ ์ํ ๋.
+* ๊ธฐ์กด ๋ฌธ์ /๋ฒ๊ทธ๋ฅผ ์์ ํ ๋.
+ * ํ
์คํธ๋ฅผ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
+* ์๋ก์ด feature๋ฅผ ์ถ๊ฐํ ๋.
+ * ํ
์คํธ๋ฅผ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
+ * ๊ด๋ จ ๋ฌธ์๊ฐ ํ์ํ๋ค๋ฉด ๋ฐ๋์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
+
+## FastAPI ์ ์ง ๊ด๋ฆฌ์ ๋์ ์ฃผ๊ธฐ
+
+**FastAPI**์ ์ ์ง ๊ด๋ฆฌ๋ฅผ ๋์์ฃผ์ธ์! ๐ค
+
+ํ ์ผ์ด ๋ง๊ณ , ๊ทธ ์ค ๋๋ถ๋ถ์ **์ฌ๋ฌ๋ถ**์ด ํ ์ ์์ต๋๋ค.
+
+์ง๊ธ ํ ์ ์๋ ์ฃผ์ ์์
์:
+
+* [GitHub์์ ๋ค๋ฅธ ์ฌ๋๋ค์ ์ง๋ฌธ์ ๋์ ์ฃผ๊ธฐ](#github_1){.internal-link target=_blank} (์์ ์น์
์ ์ฐธ์กฐํ์ธ์).
+* [Pull Request ๋ฆฌ๋ทฐํ๊ธฐ](#pull-requests){.internal-link target=_blank} (์์ ์น์
์ ์ฐธ์กฐํ์ธ์).
+
+์ด ๋ ์์
์ด **๊ฐ์ฅ ๋ง์ ์๊ฐ์ ์๋ชจ**ํ๋ ์ผ์
๋๋ค. ๊ทธ๊ฒ์ด FastAPI ์ ์ง ๊ด๋ฆฌ์ ์ฃผ์ ์์
์
๋๋ค.
+
+์ด ์์
์ ๋์์ฃผ์ ๋ค๋ฉด, **FastAPI ์ ์ง ๊ด๋ฆฌ์ ๋์์ ์ฃผ๋ ๊ฒ**์ด๋ฉฐ ๊ทธ๊ฒ์ด **๋ ๋น ๋ฅด๊ณ ๋ ์ ๋ฐ์ ํ๋ ๊ฒ**์ ๋ณด์ฅํ๋ ๊ฒ์
๋๋ค. ๐
+
+## ์ฑํ
์ ์ฐธ์ฌํ์ญ์์ค
+
+๐ฅ
๋์ค์ฝ๋ ์ฑํ
์๋ฒ ๐ฅ ์ ๊ฐ์
ํ๊ณ FastAPI ์ปค๋ฎค๋ํฐ์์ ๋ค๋ฅธ ์ฌ๋๋ค๊ณผ ์ด์ธ๋ฆฌ์ธ์.
+
+/// tip
+
+์ง๋ฌธ์ด ์๋ ๊ฒฝ์ฐ,
GitHub ๋์ค์ปค์
์์ ์ง๋ฌธํ์ญ์์ค, [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank} ์ ๋์์ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
+
+๋ค๋ฅธ ์ผ๋ฐ์ ์ธ ๋ํ์์๋ง ์ฑํ
์ ์ฌ์ฉํ์ญ์์ค.
+
+///
+
+### ์ง๋ฌธ์ ์ํด ์ฑํ
์ ์ฌ์ฉํ์ง ๋ง์ญ์์ค
+
+์ฑํ
์ ๋ ๋ง์ "์์ ๋ก์ด ๋ํ"๋ฅผ ํ์ฉํ๊ธฐ ๋๋ฌธ์, ๋๋ฌด ์ผ๋ฐ์ ์ธ ์ง๋ฌธ์ด๋ ๋๋ตํ๊ธฐ ์ด๋ ค์ด ์ง๋ฌธ์ ์ฝ๊ฒ ์ง๋ฌธ์ ํ ์ ์์ผ๋ฏ๋ก, ๋ต๋ณ์ ๋ฐ์ง ๋ชปํ ์ ์์ต๋๋ค.
+
+GitHub ์ด์์์์ ํ
ํ๋ฆฟ์ ์ฌ๋ฐ๋ฅธ ์ง๋ฌธ์ ์์ฑํ๋๋ก ์๋ดํ์ฌ ๋ ์ฝ๊ฒ ์ข์ ๋ต๋ณ์ ์ป๊ฑฐ๋ ์ง๋ฌธํ๊ธฐ ์ ์ ์ค์ค๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์๋ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ GitHub์์๋ ์๊ฐ์ด ์กฐ๊ธ ๊ฑธ๋ฆฌ๋๋ผ๋ ํญ์ ๋ชจ๋ ๊ฒ์ ๋ตํ ์ ์์ต๋๋ค. ์ฑํ
์์คํ
์์๋ ๊ฐ์ธ์ ์ผ๋ก ๊ทธ๋ ๊ฒ ํ ์ ์์ต๋๋ค. ๐
+
+์ฑํ
์์คํ
์์์ ๋ํ ๋ํ GitHub์์ ์ฒ๋ผ ์ฝ๊ฒ ๊ฒ์ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ํ ์ค์ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ด ์์ค๋ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ GitHub ์ด์์ ์๋ ๊ฒ๋ง [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}๊ฐ ๋๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ๋๋ฏ๋ก, GitHub ์ด์์์ ๋ ๋ง์ ๊ด์ฌ์ ๋ฐ์ ๊ฒ์
๋๋ค.
+
+๋ฐ๋ฉด, ์ฑํ
์์คํ
์๋ ์์ฒ ๋ช
์ ์ฌ์ฉ์๊ฐ ์๊ธฐ ๋๋ฌธ์, ๊ฑฐ์ ํญ์ ๋ํ ์๋๋ฅผ ์ฐพ์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ๐
+
+## ๊ฐ๋ฐ์ ์คํฐ์๊ฐ ๋์ญ์์ค
+
+
GitHub ์คํฐ์ ๋ฅผ ํตํด ๊ฐ๋ฐ์๋ฅผ ๊ฒฝ์ ์ ์ผ๋ก ์ง์ํ ์ ์์ต๋๋ค.
+
+๊ฐ์ฌํ๋ค๋ ๋ง๋ก ์ปคํผ๋ฅผ โ๏ธ ํ์ ์ฌ์ค ์ ์์ต๋๋ค. ๐
+
+๋ํ FastAPI์ ์ค๋ฒ ๋๋ ๊ณจ๋ ์คํฐ์๊ฐ ๋ ์ ์์ต๋๋ค. ๐
๐
+
+## FastAPI๋ฅผ ๊ฐํํ๋ ๋๊ตฌ์ ์คํฐ์๊ฐ ๋์ญ์์ค
+
+๋ฌธ์์์ ๋ณด์๋ฏ์ด, FastAPI๋ Starlette๊ณผ Pydantic ๋ผ๋ ๊ฑฐ์ธ์ ์ด๊นจ์ ํ๊ณ ์์ต๋๋ค.
+
+๋ค์์ ์คํฐ์๊ฐ ๋ ์ ์์ต๋๋ค
+
+*
Samuel Colvin (Pydantic)
+*
Encode (Starlette, Uvicorn)
+
+---
+
+๊ฐ์ฌํฉ๋๋ค! ๐
diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md
new file mode 100644
index 000000000..ff174937d
--- /dev/null
+++ b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -0,0 +1,275 @@
+# yield๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ
+
+FastAPI๋
์์
์๋ฃ ํ ์ถ๊ฐ ๋จ๊ณ๋ฅผ ์ํํ๋ ์์กด์ฑ์ ์ง์ํฉ๋๋ค.
+
+์ด๋ฅผ ๊ตฌํํ๋ ค๋ฉด `return` ๋์ `yield`๋ฅผ ์ฌ์ฉํ๊ณ , ์ถ๊ฐ๋ก ์คํํ ๋จ๊ณ (์ฝ๋)๋ฅผ ๊ทธ ๋ค์ ์์ฑํ์ธ์.
+
+/// tip | ํ
+
+๊ฐ ์์กด์ฑ๋ง๋ค `yield`๋ ํ ๋ฒ๋ง ์ฌ์ฉํด์ผ ํฉ๋๋ค.
+
+///
+
+/// note | ๊ธฐ์ ์ธ๋ถ์ฌํญ
+
+๋ค์๊ณผ ํจ๊ป ์ฌ์ฉํ ์ ์๋ ๋ชจ๋ ํจ์:
+
+*
`@contextlib.contextmanager` ๋๋
+*
`@contextlib.asynccontextmanager`
+
+๋ **FastAPI**์ ์์กด์ฑ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+์ฌ์ค, FastAPI๋ ๋ด๋ถ์ ์ผ๋ก ์ด ๋ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
+
+///
+
+## `yield`๋ฅผ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์กด์ฑ
+
+์๋ฅผ ๋ค์ด, ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์
์ ์์ฑํ๊ณ ์์
์ด ๋๋ ํ์ ์ธ์
์ ์ข
๋ฃํ ์ ์์ต๋๋ค.
+
+์๋ต์ ์์ฑํ๊ธฐ ์ ์๋ `yield`๋ฌธ์ ํฌํจํ์ฌ ๊ทธ ์ด์ ์ ์ฝ๋๋ง์ด ์คํ๋ฉ๋๋ค:
+
+{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
+
+yield๋ ๊ฐ์ *๊ฒฝ๋ก ์์
* ๋ฐ ๋ค๋ฅธ ์์กด์ฑ๋ค์ ์ฃผ์
๋๋ ๊ฐ ์
๋๋ค:
+
+{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
+
+`yield`๋ฌธ ๋ค์์ ์ฝ๋๋ ์๋ต์ ์์ฑํ ํ ๋ณด๋ด๊ธฐ ์ ์ ์คํ๋ฉ๋๋ค:
+
+{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
+
+/// tip | ํ
+
+`async` ํจ์์ ์ผ๋ฐ ํจ์ ๋ชจ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+**FastAPI**๋ ์ผ๋ฐ ์์กด์ฑ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ๊ฐ์ ํจ์๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ ๊ฒ์
๋๋ค.
+
+///
+
+## `yield`์ `try`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ
+
+`yield`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ์์ `try` ๋ธ๋ก์ ์ฌ์ฉํ๋ค๋ฉด, ์์กด์ฑ์ ์ฌ์ฉํ๋ ๋์ค ๋ฐ์ํ ๋ชจ๋ ์์ธ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
+
+์๋ฅผ ๋ค์ด, ๋ค๋ฅธ ์์กด์ฑ์ด๋ *๊ฒฝ๋ก ์์
*์ ์ค๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์
"๋กค๋ฐฑ"์ด ๋ฐ์ํ๊ฑฐ๋ ๋ค๋ฅธ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค๋ฉด, ํด๋น ์์ธ๋ฅผ ์์กด์ฑ์์ ๋ฐ์ ์ ์์ต๋๋ค.
+
+๋ฐ๋ผ์, ์์กด์ฑ ๋ด์์ `except SomeException`์ ์ฌ์ฉํ์ฌ ํน์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
+
+๋ง์ฐฌ๊ฐ์ง๋ก, `finally`๋ฅผ ์ฌ์ฉํ์ฌ ์์ธ ๋ฐ์ ์ฌ๋ถ์ ๊ด๊ณ ์์ด ์ข
๋ฃ ๋จ๊ณ๊น ์คํ๋๋๋ก ํ ์ ์์ต๋๋ค.
+
+{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
+
+## `yield`๋ฅผ ์ฌ์ฉํ๋ ํ์ ์์กด์ฑ
+
+๋ชจ๋ ํฌ๊ธฐ์ ํํ์ ํ์ ์์กด์ฑ๊ณผ ํ์ ์์กด์ฑ์ "ํธ๋ฆฌ"๋ ๊ฐ์ง ์ ์์ผ๋ฉฐ, ์ด๋ค ๋ชจ๋๊ฐ `yield`๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+**FastAPI**๋ `yield`๋ฅผ ์ฌ์ฉํ๋ ๊ฐ ์์กด์ฑ์ "์ข
๋ฃ ์ฝ๋"๊ฐ ์ฌ๋ฐ๋ฅธ ์์๋ก ์คํ๋๋๋ก ๋ณด์ฅํฉ๋๋ค.
+
+์๋ฅผ ๋ค์ด, `dependency_c`๋ `dependency_b`์ ์์กดํ ์ ์๊ณ , `dependency_b`๋ `dependency_a`์ ์์กดํ ์ ์์ต๋๋ค.
+
+{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
+
+์ด๋ค ๋ชจ๋๋ `yield`๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
+
+์ด ๊ฒฝ์ฐ `dependency_c`๋ ์ข
๋ฃ ์ฝ๋๋ฅผ ์คํํ๊ธฐ ์ํด, `dependency_b`์ ๊ฐ (์ฌ๊ธฐ์๋ `dep_b`๋ก ๋ช
๋ช
)์ด ์ฌ์ ํ ์ฌ์ฉ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค.
+
+๊ทธ๋ฆฌ๊ณ , `dependency_b`๋ ์ข
๋ฃ ์ฝ๋๋ฅผ ์ํด `dependency_a`์ ๊ฐ (์ฌ๊ธฐ์๋ `dep_a`๋ก ๋ช
๋ช
) ์ด ์ฌ์ฉ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค.
+
+{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
+
+๊ฐ์ ๋ฐฉ์์ผ๋ก, `yield`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ๊ณผ `return`์ ์ฌ์ฉํ๋ ์์กด์ฑ์ ํจ๊ป ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ด๋ค ์ค ์ผ๋ถ๊ฐ ๋ค๋ฅธ ๊ฒ๋ค์ ์์กดํ ์ ์์ต๋๋ค.
+
+๊ทธ๋ฆฌ๊ณ `yield`๋ฅผ ์ฌ์ฉํ๋ ๋ค๋ฅธ ์ฌ๋ฌ ์์กด์ฑ์ ํ์๋ก ํ๋ ๋จ์ผ ์์กด์ฑ์ ๊ฐ์ง ์๋ ์์ต๋๋ค.
+
+์ํ๋ ์์กด์ฑ์ ์ํ๋ ๋๋ก ์กฐํฉํ ์ ์์ต๋๋ค.
+
+**FastAPI**๋ ๋ชจ๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅธ ์์๋ก ์คํ๋๋๋ก ๋ณด์ฅํฉ๋๋ค.
+
+/// note | ๊ธฐ์ ์ธ๋ถ์ฌํญ
+
+ํ์ด์ฌ์
Context Managers ๋๋ถ์ ์ด ๊ธฐ๋ฅ์ด ์๋ํฉ๋๋ค.
+
+**FastAPI**๋ ์ด๋ฅผ ๋ด๋ถ์ ์ผ๋ก ์ปจํ
์คํธ ๊ด๋ฆฌ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํฉ๋๋ค.
+
+///
+
+## `yield`์ `HTTPException`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ
+
+`yield`์ `try` ๋ธ๋ก์ด ์๋ ์์กด์ฑ์ ์ฌ์ฉํ์ฌ ์์ธ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋์์ต๋๋ค.
+
+๊ฐ์ ๋ฐฉ์์ผ๋ก, `yield` ์ดํ์ ์ข
๋ฃ ์ฝ๋์์ `HTTPException`์ด๋ ์ ์ฌํ ์์ธ๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๋ค.
+
+/// tip | ํ
+
+์ด๋ ๋ค์ ๊ณ ๊ธ ๊ธฐ์ ์ด๋ฉฐ, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๊ฒฝ๋ก ์ฐ์ฐ ํจ์ ๋ฑ ๋๋จธ์ง ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ด๋ถ์์ ์์ธ (`HTTPException` ํฌํจ)๋ฅผ ๋ฐ์์ํฌ ์ ์์ผ๋ฏ๋ก ์ค์ ๋ก๋ ํ์ํ์ง ์์ ๊ฒ์
๋๋ค.
+
+ํ์ง๋ง ํ์ํ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๐ค
+
+///
+
+{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
+
+์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ณ (๋๋ ์ถ๊ฐ๋ก ๋ค๋ฅธ `HTTPException`์ ๋ฐ์์ํค๊ธฐ ์ํด) ์ฌ์ฉํ ์ ์๋ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ [์ฌ์ฉ์ ์ ์ ์์ธ ์ฒ๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}๋ฅผ ์์ฑํ๋ ๊ฒ ์
๋๋ค.
+
+## `yield`์ `except`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ
+
+`yield`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ์์ `except`๋ฅผ ์ฌ์ฉํ์ฌ ์์ธ๋ฅผ ํฌ์ฐฉํ๊ณ ์์ธ๋ฅผ ๋ค์ ๋ฐ์์ํค์ง ์๊ฑฐ๋ (๋๋ ์ ์์ธ๋ฅผ ๋ฐ์์ํค์ง ์์ผ๋ฉด), FastAPI๋ ํด๋น ์์ธ๊ฐ ๋ฐ์ํ๋์ง ์ ์ ์์ต๋๋ค. ์ด๋ ์ผ๋ฐ์ ์ธ Python ๋ฐฉ์๊ณผ ๋์ผํฉ๋๋ค:
+
+{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
+
+์ด ๊ฒฝ์ฐ, `HTTPException`์ด๋ ์ ์ฌํ ์์ธ๋ฅผ ๋ฐ์์ํค์ง ์๊ธฐ ๋๋ฌธ์ ํด๋ผ์ด์ธํธ๋ HTTP 500 Internal Server Error ์๋ต์ ๋ณด๊ฒ ๋์ง๋ง, ์๋ฒ๋ ์ด๋ค ์ค๋ฅ๊ฐ ๋ฐ์ํ๋์ง์ ๋ํ **๋ก๊ทธ**๋ ๋ค๋ฅธ ํ์๋ฅผ ์ ํ ๊ฐ์ง์ง ์๊ฒ ๋ฉ๋๋ค. ๐ฑ
+
+### `yield`์ `except`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ์์ ํญ์ `raise` ํ๊ธฐ
+
+`yield`๊ฐ ์๋ ์์กด์ฑ์์ ์์ธ๋ฅผ ์ก์์ ๋๋ `HTTPException`์ด๋ ์ ์ฌํ ์์ธ๋ฅผ ์๋ก ๋ฐ์์ํค์ง ์๋ ํ, ๋ฐ๋์ ์๋์ ์์ธ๋ฅผ ๋ค์ ๋ฐ์์์ผ์ผ ํฉ๋๋ค.
+
+`raise`๋ฅผ ์ฌ์ฉํ์ฌ ๋์ผํ ์์ธ๋ฅผ ๋ค์ ๋ฐ์์ํฌ ์ ์์ต๋๋ค:
+
+{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
+
+์ด์ ํด๋ผ์ด์ธํธ๋ ๋์ผํ *HTTP 500 Internal Server Error* ์ค๋ฅ ์๋ต์ ๋ฐ๊ฒ ๋์ง๋ง, ์๋ฒ ๋ก๊ทธ์๋ ์ฌ์ฉ์ ์ ์ ์์ธ์ธ `InternalError"๊ฐ ๊ธฐ๋ก๋ฉ๋๋ค. ๐
+
+## `yield`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ์ ์คํ ์์
+
+์คํ ์์๋ ์๋ ๋ค์ด์ด๊ทธ๋จ๊ณผ ๊ฑฐ์ ๋น์ทํฉ๋๋ค. ์๊ฐ์ ์์์ ์๋๋ก ํ๋ฆ
๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ฐ ์ด์ ์ํธ ์์ฉํ๊ฑฐ๋ ์ฝ๋๋ฅผ ์คํํ๋ ๋ถ๋ถ ์ค ํ๋์
๋๋ค.
+
+```mermaid
+sequenceDiagram
+
+participant client as Client
+participant handler as Exception handler
+participant dep as Dep with yield
+participant operation as Path Operation
+participant tasks as Background tasks
+
+ Note over client,operation: Can raise exceptions, including HTTPException
+ client ->> dep: Start request
+ Note over dep: Run code up to yield
+ opt raise Exception
+ dep -->> handler: Raise Exception
+ handler -->> client: HTTP error response
+ end
+ dep ->> operation: Run dependency, e.g. DB session
+ opt raise
+ operation -->> dep: Raise Exception (e.g. HTTPException)
+ opt handle
+ dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
+ end
+ handler -->> client: HTTP error response
+ end
+
+ operation ->> client: Return response to client
+ Note over client,operation: Response is already sent, can't change it anymore
+ opt Tasks
+ operation -->> tasks: Send background tasks
+ end
+ opt Raise other exception
+ tasks -->> tasks: Handle exceptions in the background task code
+ end
+```
+
+/// info | ์ ๋ณด
+
+ํด๋ผ์ด์ธํธ์ **ํ๋์ ์๋ต** ๋ง ์ ์ก๋ฉ๋๋ค. ์ด๋ ์ค๋ฅ ์๋ต ์ค ํ๋์ผ ์๋ ์๊ณ ,*๊ฒฝ๋ก ์์
*์์ ์์ฑ๋ ์๋ต์ผ ์๋ ์์ต๋๋ค.
+
+์ด๋ฌํ ์๋ต ์ค ํ๋๊ฐ ์ ์ก๋ ํ์๋ ๋ค๋ฅธ ์๋ต์ ๋ณด๋ผ ์ ์์ต๋๋ค.
+
+///
+
+/// tip | ํ
+
+์ด ๋ค์ด์ด๊ทธ๋จ์ `HTTPException`์ ๋ณด์ฌ์ฃผ์ง๋ง, `yield`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ์์ ์ฒ๋ฆฌํ ์์ธ๋ [์ฌ์ฉ์ ์ ์ ์์ธ์ฒ๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํ ๋ค๋ฅธ ์์ธ๋ ๋ฐ์์ํฌ ์ ์์ต๋๋ค.
+
+์ด๋ค ์์ธ๊ฐ ๋ฐ์ํ๋ , `HTTPException`์ ํฌํจํ์ฌ yield๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์์ธ๋ฅผ ๋ค์ ๋ฐ์์ํค๊ฑฐ๋ ์๋ก์ด ์์ธ๋ฅผ ๋ฐ์์์ผ์ผ ํฉ๋๋ค.
+
+///
+
+## `yield`, `HTTPException`, `except` ๋ฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์ ์ฌ์ฉํ๋ ์์กด์ฑ
+
+/// warning | ๊ฒฝ๊ณ
+
+์ด๋ฌํ ๊ธฐ์ ์ ์ธ๋ถ ์ฌํญ์ ๋๋ถ๋ถ ํ์ํ์ง ์์ผ๋ฏ๋ก ์ด ์น์
์ ๊ฑด๋๋ฐ๊ณ ์๋์์ ๊ณ์ ์งํํด๋ ๋ฉ๋๋ค.
+
+์ด๋ฌํ ์ธ๋ถ ์ ๋ณด๋ ์ฃผ๋ก FastAPI 0.106.0 ์ด์ ๋ฒ์ ์์ `yield`๊ฐ ์๋ ์์กด์ฑ์ ๋ฆฌ์์ค๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ฉ ์ ์ฉํฉ๋๋ค.
+
+///
+
+### `yield`์ `except`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ, ๊ธฐ์ ์ธ๋ถ์ฌํญ
+
+FastAPI 0.110.0 ์ด์ ์๋ `yield`๊ฐ ํฌํจ๋ ์์กด์ฑ์ ์ฌ์ฉํ ํ ํด๋น ์์กด์ฑ์์ `except`๊ฐ ํฌํจ๋ ์์ธ๋ฅผ ์บก์ฒํ๊ณ ๋ค์ ์์ธ๋ฅผ ๋ฐ์์ํค์ง ์์ผ๋ฉด ์์ธ๊ฐ ์๋์ผ๋ก ์์ธ ํธ๋ค๋ฌ ๋๋ ๋ด๋ถ ์๋ฒ ์ค๋ฅ ํธ๋ค๋ฌ๋ก ๋ฐ์/์ ๋ฌ๋์์ต๋๋ค.
+
+์ด๋ ์ฒ๋ฆฌ๊ธฐ ์์ด ์ ๋ฌ๋ ์์ธ(๋ด๋ถ ์๋ฒ ์ค๋ฅ)์์ ์ฒ๋ฆฌ๋์ง ์์ ๋ฉ๋ชจ๋ฆฌ ์๋น๋ฅผ ์์ ํ๊ณ ์ผ๋ฐ ํ์ด์ฌ ์ฝ๋์ ๋์๊ณผ ์ผ์นํ๋๋ก ํ๊ธฐ ์ํด 0.110.0 ๋ฒ์ ์์ ๋ณ๊ฒฝ๋์์ต๋๋ค.
+
+### ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
๊ณผ `yield`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ, ๊ธฐ์ ์ธ๋ถ์ฌํญ
+
+FastAPI 0.106.0 ์ด์ ์๋ `yield` ์ดํ์ ์์ธ๋ฅผ ๋ฐ์์ํค๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ์ต๋๋ค. `yield`๊ฐ ์๋ ์์กด์ฑ ์ข
๋ฃ ์ฝ๋๋ ์๋ต์ด ์ ์ก๋ ์ดํ์ ์คํ๋์๊ธฐ ๋๋ฌธ์, [์์ธ ์ฒ๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}๊ฐ ์ด๋ฏธ ์คํ๋ ์ํ์์ต๋๋ค.
+
+์ด๋ ์ฃผ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
๋ด์์ ์์กด์ฑ์์ "yield๋" ๋์ผํ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ํ๊ธฐ ์ํด ์ด๋ฐ ๋ฐฉ์์ผ๋ก ์ค๊ณ๋์์ต๋๋ค. ์ข
๋ฃ ์ฝ๋๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์ด ์๋ฃ๋ ํ์ ์คํ๋์๊ธฐ ๋๋ฌธ์
๋๋ค
+
+ํ์ง๋ง ์ด๋ ๊ฒ ํ๋ฉด ๋ฆฌ์์ค๋ฅผ ๋ถํ์ํ๊ฒ ์๋ณดํ ์์กด์ฑ(์: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ)์์ ๋ณด์ ํ๋ฉด์ ์๋ต์ด ๋คํธ์ํฌ๋ฅผ ํตํด ์ด๋ํ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ ์๋ฏธํ๊ธฐ ๋๋ฌธ์ FastAPI 0.106.0์์ ๋ณ๊ฒฝ๋์์ต๋๋ค.
+
+/// tip | ํ
+
+๋ํ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์ ์ผ๋ฐ์ ์ผ๋ก ์์ฒด ๋ฆฌ์์ค(์: ์์ฒด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ)๋ฅผ ์ฌ์ฉํ์ฌ ๋ณ๋๋ก ์ฒ๋ฆฌํด์ผ ํ๋ ๋
๋ฆฝ์ ์ธ ๋ก์ง ์งํฉ์
๋๋ค.
+
+๋ฐ๋ผ์ ์ด๋ ๊ฒ ํ๋ฉด ์ฝ๋๊ฐ ๋ ๊น๋ํด์ง๋๋ค.
+
+///
+
+๋ง์ฝ ์ด์ ์ ์ด๋ฌํ ๋์์ ์์กดํ๋ค๋ฉด, ์ด์ ๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
๋ด๋ถ์์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์ ์ํ ๋ฆฌ์์ค๋ฅผ ์์ฑํ๊ณ , `yield`๊ฐ ์๋ ์์กด์ฑ์ ๋ฆฌ์์ค์ ์์กดํ์ง ์๋ ๋ฐ์ดํฐ๋ง ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํด์ผํฉ๋๋ค.
+
+์๋ฅผ ๋ค์ด, ๋์ผํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์
์ ์ฌ์ฉํ๋ ๋์ , ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
๋ด๋ถ์์ ์๋ก์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์
์ ์์ฑํ๊ณ ์ด ์๋ก์ด ์ธ์
์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ฒด๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
ํจ์์ ๋งค๊ฐ๋ณ์๋ก ์ง์ ์ ๋ฌํ๋ ๋์ , ํด๋น ๊ฐ์ฒด์ ID๋ฅผ ์ ๋ฌํ ๋ค์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
ํจ์ ๋ด๋ถ์์ ๊ฐ์ฒด๋ฅผ ๋ค์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค
+
+## ์ปจํ
์คํธ ๊ด๋ฆฌ์
+
+### "์ปจํ
์คํธ ๊ด๋ฆฌ์"๋?
+
+"์ปจํ
์คํธ ๊ด๋ฆฌ์"๋ Python์์ `with` ๋ฌธ์์ ์ฌ์ฉํ ์ ์๋ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ์๋ฏธํฉ๋๋ค.
+
+์๋ฅผ ๋ค์ด,
`with`๋ฅผ ์ฌ์ฉํ์ฌ ํ์ผ์ ์ฝ์ ์ ์์ต๋๋ค:
+
+```Python
+with open("./somefile.txt") as f:
+ contents = f.read()
+ print(contents)
+```
+
+๋ด๋ถ์ ์ผ๋ก `open("./somefile.txt")` ๋ "์ปจํ
์คํธ ๊ด๋ฆฌ์(Context Manager)"๋ผ๊ณ ๋ถ๋ฆฌ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค.
+
+`with` ๋ธ๋ก์ด ๋๋๋ฉด, ์์ธ๊ฐ ๋ฐ์ํ๋๋ผ๋ ํ์ผ์ ๋ซ๋๋ก ๋ณด์ฅํฉ๋๋ค.
+
+`yield`๊ฐ ์๋ ์์กด์ฑ์ ์์ฑํ๋ฉด **FastAPI**๋ ๋ด๋ถ์ ์ผ๋ก ์ด๋ฅผ ์ํ ์ปจํ
์คํธ ๋งค๋์ ๋ฅผ ์์ฑํ๊ณ ๋ค๋ฅธ ๊ด๋ จ ๋๊ตฌ๋ค๊ณผ ๊ฒฐํฉํฉ๋๋ค.
+
+### `yield`๋ฅผ ์ฌ์ฉํ๋ ์์กด์ฑ์์ ์ปจํ
์คํธ ๊ด๋ฆฌ์ ์ฌ์ฉํ๊ธฐ
+
+/// warning | ๊ฒฝ๊ณ
+
+์ด๊ฒ์ ์ด๋ ์ ๋ "๊ณ ๊ธ" ๊ฐ๋
์
๋๋ค.
+
+**FastAPI**๋ฅผ ์ฒ์ ์์ํ๋ ๊ฒฝ์ฐ ์ง๊ธ์ ์ด ๋ถ๋ถ์ ๊ฑด๋๋ฐ์ด๋ ์ข์ต๋๋ค.
+
+///
+
+Python์์๋ ๋ค์์ ํตํด ์ปจํ
์คํธ ๊ด๋ฆฌ์๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
๋ ๊ฐ์ง ๋ฉ์๋๊ฐ ์๋ ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค: `__enter__()` and `__exit__()`.
+
+**FastAPI**์ `yield`๊ฐ ์๋ ์์กด์ฑ ๋ด์์
+`with` ๋๋ `async with`๋ฌธ์ ์ฌ์ฉํ์ฌ ์ด๋ค์ ํ์ฉํ ์ ์์ต๋๋ค:
+
+{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
+
+/// tip | ํ
+
+์ปจํ
์คํธ ๊ด๋ฆฌ์๋ฅผ ์์ฑํ๋ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
+
+*
`@contextlib.contextmanager` ๋๋
+*
`@contextlib.asynccontextmanager`
+
+์ด๋ค์ ๋จ์ผ `yield`๊ฐ ์๋ ํจ์๋ฅผ ๊พธ๋ฏธ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค.
+
+์ด๊ฒ์ด **FastAPI**๊ฐ `yield`๊ฐ ์๋ ์์กด์ฑ์ ์ํด ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ์์
๋๋ค.
+
+ํ์ง๋ง FastAPI ์์กด์ฑ์๋ ์ด๋ฌํ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์ต๋๋ค(๊ทธ๋ฆฌ๊ณ ์ฌ์ฉํด์๋ ์๋ฉ๋๋ค).
+
+FastAPI๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ด๋ฅผ ์ฒ๋ฆฌํด ์ค ๊ฒ์
๋๋ค.
+
+///
diff --git a/docs/ko/docs/tutorial/security/simple-oauth2.md b/docs/ko/docs/tutorial/security/simple-oauth2.md
index ddc7430af..f10c4f588 100644
--- a/docs/ko/docs/tutorial/security/simple-oauth2.md
+++ b/docs/ko/docs/tutorial/security/simple-oauth2.md
@@ -32,7 +32,7 @@ OAuth2๋ (์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๊ณ ์๋) "ํจ์ค์๋ ํ๋ก์ฐ"์ ์ฌ์ฉํ
* `instagram_basic`์ ํ์ด์ค๋ถ/์ธ์คํ๊ทธ๋จ์์ ์ฌ์ฉํฉ๋๋ค.
* `https://www.googleapis.com/auth/drive`๋ Google์์ ์ฌ์ฉํฉ๋๋ค.
-/// ์ ๋ณด
+/// info | ์ ๋ณด
OAuth2์์ "๋ฒ์"๋ ํ์ํ ํน์ ๊ถํ์ ์ ์ธํ๋ ๋ฌธ์์ด์
๋๋ค.
@@ -61,7 +61,7 @@ OAuth2์ ๊ฒฝ์ฐ ๋ฌธ์์ด์ผ ๋ฟ์
๋๋ค.
* `scope`๋ ์ ํ์ ์ธ ํ๋๋ก ๊ณต๋ฐฑ์ผ๋ก ๊ตฌ๋ถ๋ ๋ฌธ์์ด๋ก ๊ตฌ์ฑ๋ ํฐ ๋ฌธ์์ด์
๋๋ค.
* `grant_type`(์ ํ์ ์ผ๋ก ์ฌ์ฉ).
-/// ํ
+/// tip | ํ
OAuth2 ์ฌ์์ ์ค์ ๋ก `password`๋ผ๋ ๊ณ ์ ๊ฐ์ด ์๋ `grant_type` ํ๋๋ฅผ *์๊ตฌ*ํ์ง๋ง `OAuth2PasswordRequestForm`์ ์ด๋ฅผ ๊ฐ์ํ์ง ์์ต๋๋ค.
@@ -72,7 +72,7 @@ OAuth2 ์ฌ์์ ์ค์ ๋ก `password`๋ผ๋ ๊ณ ์ ๊ฐ์ด ์๋ `grant_type`
* `client_id`(์ ํ์ ์ผ๋ก ์ฌ์ฉ) (์์ ์์๋ ํ์ํ์ง ์์ต๋๋ค).
* `client_secret`(์ ํ์ ์ผ๋ก ์ฌ์ฉ) (์์ ์์๋ ํ์ํ์ง ์์ต๋๋ค).
-/// ์ ๋ณด
+/// info | ์ ๋ณด
`OAuth2PasswordRequestForm`์ `OAuth2PasswordBearer`์ ๊ฐ์ด **FastAPI**์ ๋ํ ํน์ ํด๋์ค๊ฐ ์๋๋๋ค.
@@ -86,7 +86,7 @@ OAuth2 ์ฌ์์ ์ค์ ๋ก `password`๋ผ๋ ๊ณ ์ ๊ฐ์ด ์๋ `grant_type`
### ํผ ๋ฐ์ดํฐ ์ฌ์ฉํ๊ธฐ
-/// ํ
+/// tip | ํ
์ข
์์ฑ ํด๋์ค `OAuth2PasswordRequestForm`์ ์ธ์คํด์ค์๋ ๊ณต๋ฐฑ์ผ๋ก ๊ตฌ๋ถ๋ ๊ธด ๋ฌธ์์ด์ด ์๋ `scope` ์์ฑ์ด ์๊ณ ๋์ ์ ์ก๋ ๊ฐ ๋ฒ์์ ๋ํ ์ค์ ๋ฌธ์์ด ๋ชฉ๋ก์ด ์๋ `scopes` ์์ฑ์ด ์์ต๋๋ค.
@@ -126,7 +126,7 @@ OAuth2 ์ฌ์์ ์ค์ ๋ก `password`๋ผ๋ ๊ณ ์ ๊ฐ์ด ์๋ `grant_type`
๋ฐ๋ผ์ ํด์ปค๋ ๋ค๋ฅธ ์์คํ
์์ ๋์ผํ ์ํธ๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ์๋ํ ์ ์์ต๋๋ค(๋ง์ ์ฌ์ฉ์๊ฐ ๋ชจ๋ ๊ณณ์์ ๋์ผํ ์ํธ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ์ด๋ ์ํํ ์ ์์ต๋๋ค).
-//// tab | Pํ์ด์ฌ 3.7 ์ด์
+//// tab | ํ์ด์ฌ 3.7 ์ด์
{* ../../docs_src/security/tutorial003.py hl[80:83] *}
@@ -150,7 +150,7 @@ UserInDB(
)
```
-/// ์ ๋ณด
+/// info | ์ ๋ณด
`**user_dict`์ ๋ํ ์์ธํ ์ค๋ช
์ [**์ถ๊ฐ ๋ชจ๋ธ** ๋ฌธ์](../extra-models.md#about-user_indict){.internal-link target=_blank}๋ฅผ ๋ค์ ์ฝ์ด๋ด
์๋ค.
@@ -166,7 +166,7 @@ UserInDB(
์ด ๊ฐ๋จํ ์์ ์์๋ ์์ ํ ์์ ํ์ง ์๊ณ , ๋์ผํ `username`์ ํ ํฐ์ผ๋ก ๋ฐํํฉ๋๋ค.
-/// ํ
+/// tip | ํ
๋ค์ ์ฅ์์๋ ํจ์ค์๋ ํด์ฑ ๋ฐ
JWT ํ ํฐ์ ์ฌ์ฉํ์ฌ ์ค์ ๋ณด์ ๊ตฌํ์ ๋ณผ ์ ์์ต๋๋ค.
@@ -176,7 +176,7 @@ UserInDB(
{* ../../docs_src/security/tutorial003.py hl[85] *}
-/// ํ
+/// tip | ํ
์ฌ์์ ๋ฐ๋ผ ์ด ์์ ์ ๋์ผํ๊ฒ `access_token` ๋ฐ `token_type`์ด ํฌํจ๋ JSON์ ๋ฐํํด์ผ ํฉ๋๋ค.
@@ -202,7 +202,7 @@ UserInDB(
{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *}
-/// ์ ๋ณด
+/// info | ์ ๋ณด
์ฌ๊ธฐ์ ๋ฐํํ๋ ๊ฐ์ด `Bearer`์ธ ์ถ๊ฐ ํค๋ `WWW-Authenticate`๋ ์ฌ์์ ์ผ๋ถ์
๋๋ค.
diff --git a/docs/pt/docs/deployment/https.md b/docs/pt/docs/deployment/https.md
index 9a13977ec..9ad419934 100644
--- a/docs/pt/docs/deployment/https.md
+++ b/docs/pt/docs/deployment/https.md
@@ -14,38 +14,186 @@ Para aprender o bรกsico de HTTPS de uma perspectiva do usuรกrio, verifique
SNI.
- * Esta extensรฃo SNI permite que um รบnico servidor (com um รบnico endereรงo IP) tenha vรกrios certificados HTTPS e atenda a vรกrios domรญnios / aplicativos HTTPS.
- * Para que isso funcione, um รบnico componente (programa) em execuรงรฃo no servidor, ouvindo no endereรงo IP pรบblico, deve ter todos os certificados HTTPS no servidor.
-* Depois de obter uma conexรฃo segura, o protocolo de comunicaรงรฃo ainda รฉ HTTP.
- * Os conteรบdos sรฃo criptografados, embora sejam enviados com o protocolo HTTP.
+ * No entanto, existe uma **soluรงรฃo** para isso.
+* Hรก uma **extensรฃo** para o protocolo **TLS** (aquele que lida com a criptografia no nรญvel TCP, antes do HTTP) chamado **
SNI**.
+ * Esta extensรฃo SNI permite que um รบnico servidor (com um **รบnico endereรงo IP**) tenha **vรกrios certificados HTTPS** e atenda a **vรกrios domรญnios / aplicativos HTTPS**.
+ * Para que isso funcione, um **รบnico** componente (programa) em execuรงรฃo no servidor, ouvindo no **endereรงo IP pรบblico**, deve ter **todos os certificados HTTPS** no servidor.
+* **Depois** de obter uma conexรฃo segura, o protocolo de comunicaรงรฃo **ainda รฉ HTTP**.
+ * Os conteรบdos sรฃo **criptografados**, embora sejam enviados com o **protocolo HTTP**.
-ร uma prรกtica comum ter um programa/servidor HTTP em execuรงรฃo no servidor (mรกquina, host, etc.) e gerenciar todas as partes HTTPS: enviando as solicitaรงรตes HTTP descriptografadas para o aplicativo HTTP real em execuรงรฃo no mesmo servidor (a aplicaรงรฃo **FastAPI**, neste caso), pegue a resposta HTTP do aplicativo, criptografe-a usando o certificado apropriado e envie-a de volta ao cliente usando HTTPS. Este servidor รฉ frequentemente chamado de
TLS Termination Proxy.
+ร uma prรกtica comum ter um **programa/servidor HTTP** em execuรงรฃo no servidor (mรกquina, host, etc.) e **gerenciar todas as partes HTTPS**: **recebendo as requisiรงรตes encriptadas**, enviando as **solicitaรงรตes HTTP descriptografadas** para o aplicativo HTTP real em execuรงรฃo no mesmo servidor (a aplicaรงรฃo **FastAPI**, neste caso), pegue a **resposta HTTP** do aplicativo, **criptografe-a** usando o **certificado HTTPS** apropriado e envie-a de volta ao cliente usando **HTTPS**. Este servidor รฉ frequentemente chamado de **
Proxy de Terminaรงรฃo TLS**.
+
+Algumas das opรงรตes que vocรช pode usar como Proxy de Terminaรงรฃo TLS sรฃo:
+
+* Traefik (que tambรฉm pode gerenciar a renovaรงรฃo de certificados)
+* Caddy (que tambรฉm pode gerenciar a renovaรงรฃo de certificados)
+* Nginx
+* HAProxy
## Let's Encrypt
-Antes de Let's Encrypt, esses certificados HTTPS eram vendidos por terceiros confiรกveis.
+Antes de Let's Encrypt, esses **certificados HTTPS** eram vendidos por terceiros confiรกveis.
O processo de aquisiรงรฃo de um desses certificados costumava ser complicado, exigia bastante papelada e os certificados eram bastante caros.
-Mas entรฃo
Let's Encrypt foi criado.
+Mas entรฃo o **
Let's Encrypt** foi criado.
-Ele รฉ um projeto da Linux Foundation que fornece certificados HTTPS gratuitamente. De forma automatizada. Esses certificados usam toda a seguranรงa criptogrรกfica padrรฃo e tรชm vida curta (cerca de 3 meses), entรฃo a seguranรงa รฉ realmente melhor por causa de sua vida รบtil reduzida.
+Ele รฉ um projeto da Linux Foundation que fornece **certificados HTTPS gratuitamente** . De forma automatizada. Esses certificados usam toda a seguranรงa criptogrรกfica padrรฃo e tรชm vida curta (cerca de 3 meses), entรฃo a **seguranรงa รฉ, na verdade, melhor** por causa de sua vida รบtil reduzida.
Os domรญnios sรฃo verificados com seguranรงa e os certificados sรฃo gerados automaticamente. Isso tambรฉm permite automatizar a renovaรงรฃo desses certificados.
-A ideia รฉ automatizar a aquisiรงรฃo e renovaรงรฃo desses certificados, para que vocรช tenha HTTPS seguro, de graรงa e para sempre.
+A ideia รฉ automatizar a aquisiรงรฃo e renovaรงรฃo desses certificados, para que vocรช tenha **HTTPS seguro, de graรงa e para sempre**.
+
+## HTTPS para Desenvolvedores
+
+Aqui estรก um exemplo de como uma API HTTPS poderia ser estruturada, passo a passo, com foco principal nas ideias relevantes para desenvolvedores.
+
+### Nome do domรญnio
+
+A etapa inicial provavelmente seria **adquirir** algum **nome de domรญnio**. Entรฃo, vocรช iria configurรก-lo em um servidor DNS (possivelmente no mesmo provedor em nuvem).
+
+Vocรช provavelmente usaria um servidor em nuvem (mรกquina virtual) ou algo parecido, e ele teria
fixed **Endereรงo IP pรบblico**.
+
+No(s) servidor(es) DNS, vocรช configuraria um registro (`registro A`) para apontar **seu domรญnio** para o **endereรงo IP pรบblico do seu servidor**.
+
+Vocรช provavelmente farรก isso apenas uma vez, na primeira vez em que tudo estiver sendo configurado.
+
+/// tip | Dica
+
+Essa parte do Nome do Domรญnio se dรก muito antes do HTTPS, mas como tudo depende do domรญnio e endereรงo IP pรบblico, vale a pena mencionรก-la aqui.
+
+///
+
+### DNS
+
+Agora vamos focar em todas as partes que realmente fazem parte do HTTPS.
+
+Primeiro, o navegador iria verificar com os **servidores DNS** qual o **IP do domรญnio**, nesse caso, `someapp.example.com`.
+
+Os servidores DNS iriam informar o navegador para utilizar algum **endereรงo IP** especรญfico. Esse seria o endereรงo IP pรบblico em uso no seu servidor, que vocรช configurou nos servidores DNS.
+
+

+
+### Inรญcio do Handshake TLS
+
+O navegador entรฃo irรก comunicar-se com esse endereรงo IP na **porta 443** (a porta HTTPS).
+
+A primeira parte dessa comunicaรงรฃo รฉ apenas para estabelecer a conexรฃo entre o cliente e o servidor e para decidir as chaves criptogrรกficas a serem utilizadas, etc.
+
+

+
+Esse interaรงรฃo entre o cliente e o servidor para estabelecer uma conexรฃo TLS รฉ chamada de **Handshake TLS**.
+
+### TLS com a Extensรฃo SNI
+
+**Apenas um processo** no servidor pode se conectar a uma **porta** em um **endereรงo IP**. Poderiam existir outros processos conectados em outras portas desse mesmo endereรงo IP, mas apenas um para cada combinaรงรฃo de endereรงo IP e porta.
+
+TLS (HTTPS) usa a porta `443` por padrรฃo. Entรฃo essa รฉ a porta que precisamos.
+
+Como apenas um รบnico processo pode se comunicar com essa porta, o processo que faria isso seria o **Proxy de Terminaรงรฃo TLS**.
+
+O Proxy de Terminaรงรฃo TLS teria acesso a um ou mais **certificados TLS** (certificados HTTPS).
+
+Utilizando a **extensรฃo SNI** discutida acima, o Proxy de Terminaรงรฃo TLS iria checar qual dos certificados TLS (HTTPS) disponรญveis deve ser usado para essa conexรฃo, utilizando o que corresponda ao domรญnio esperado pelo cliente.
+
+Nesse caso, ele usaria o certificado para `someapp.example.com`.
+
+

+
+O cliente jรก **confia** na entidade que gerou o certificado TLS (nesse caso, o Let's Encrypt, mas veremos sobre isso mais tarde), entรฃo ele pode **verificar** que o certificado รฉ vรกlido.
+
+Entรฃo, utilizando o certificado, o cliente e o Proxy de Terminaรงรฃo TLS **decidem como encriptar** o resto da **comunicaรงรฃo TCP**. Isso completa a parte do **Handshake TLS**.
+
+Apรณs isso, o cliente e o servidor possuem uma **conexรฃo TCP encriptada**, que รฉ provida pelo TLS. E entรฃo eles podem usar essa conexรฃo para comeรงar a **comunicaรงรฃo HTTP** propriamente dita.
+
+E isso resume o que รฉ **HTTPS**, apenas **HTTP** simples dentro de uma **conexรฃo TLS segura** em vez de uma conexรฃo TCP pura (nรฃo encriptada).
+
+/// tip | Dica
+
+Percebe que a encriptaรงรฃo da comunicaรงรฃo acontece no **nรญvel do TCP**, nรฃo no nรญvel do HTTP.
+
+///
+
+### Solicitaรงรฃo HTTPS
+
+Agora que o cliente e servidor (especialmente o navegador e o Proxy de Terminaรงรฃo TLS) possuem uma **conexรฃo TCP encriptada**, eles podem iniciar a **comunicaรงรฃo HTTP**.
+
+Entรฃo, o cliente envia uma **solicitaรงรฃo HTTPS**. Que รฉ apenas uma solicitaรงรฃo HTTP sobre uma conexรฃo TLS encriptada.
+
+

+
+### Desencriptando a Solicitaรงรฃo
+
+O Proxy de Terminaรงรฃo TLS entรฃo usaria a encriptaรงรฃo combinada para **desencriptar a solicitaรงรฃo**, e transmitiria a **solicitaรงรฃo bรกsica (desencriptada)** para o processo executando a aplicaรงรฃo (por exemplo, um processo com Uvicorn executando a aplicaรงรฃo FastAPI).
+
+

+
+### Resposta HTTP
+
+A aplicaรงรฃo processaria a solicitaรงรฃo e retornaria uma **resposta HTTP bรกsica (nรฃo encriptada)** para o Proxy de Terminaรงรฃo TLS.
+
+

+
+### Resposta HTTPS
+
+O Proxy de Terminaรงรฃo TLS iria **encriptar a resposta** utilizando a criptografia combinada anteriormente (que foi definida com o certificado para `someapp.example.com`), e devolveria para o navegador.
+
+No prรณximo passo, o navegador verifica que a resposta รฉ vรกlida e encriptada com a chave criptogrรกfica correta, etc. E depois **desencripta a resposta** e a processa.
+
+

+
+O cliente (navegador) saberรก que a resposta vem do servidor correto por que ela usa a criptografia que foi combinada entre eles usando o **certificado HTTPS** anterior.
+
+### Mรบltiplas Aplicaรงรตes
+
+Podem existir **mรบltiplas aplicaรงรตes** em execuรงรฃo no mesmo servidor (ou servidores), por exemplo: outras APIs ou um banco de dados.
+
+Apenas um processo pode estar vinculado a um IP e porta (o Proxy de Terminaรงรฃo TLS, por exemplo), mas outras aplicaรงรตes/processos tambรฉm podem estar em execuรงรฃo no(s) servidor(es), desde que nรฃo tentem usar a mesma **combinaรงรฃo de IP pรบblico e porta**.
+
+

+
+Dessa forma, o Proxy de Terminaรงรฃo TLS pode gerenciar o HTTPS e os certificados de **mรบltiplos domรญnios**, para mรบltiplas aplicaรงรตes, e entรฃo transmitir as requisiรงรตes para a aplicaรงรฃo correta em cada caso.
+
+### Renovaรงรฃo de Certificados
+
+Em algum momento futuro, cada certificado irรก **expirar** (aproximadamente 3 meses apรณs a aquisiรงรฃo).
+
+E entรฃo, haverรก outro programa (em alguns casos pode ser o prรณprio Proxy de Terminaรงรฃo TLS) que irรก interagir com o Let's Encrypt e renovar o(s) certificado(s).
+
+

+
+Os **certificados TLS** sรฃo **associados com um nome de domรญnio**, e nรฃo a um endereรงo IP.
+
+Entรฃo para renovar os certificados, o programa de renovaรงรฃo precisa **provar** para a autoridade (Let's Encrypt) que ele realmente **possui e controla esse domรญnio**>
+
+Para fazer isso, e acomodar as necessidades de diferentes aplicaรงรตes, existem diferentes opรงรตes para esse programa. Algumas escolhas populares sรฃo:
+
+* **Modificar alguns registros DNS**
+ * Para isso, o programa de renovaรงรฃo precisa ter suporte as APIs do provedor DNS, entรฃo, dependendo do provedor DNS que vocรช utilize, isso pode ou nรฃo ser uma opรงรฃo viรกvel.
+* **Executar como um servidor** (ao menos durante o processo de aquisiรงรฃo do certificado) no endereรงo IP pรบblico associado com o domรญnio.
+ * Como dito anteriormente, apenas um processo pode estar ligado a uma porta e IP especรญficos.
+ * Essa รฉ uma dos motivos que fazem utilizar o mesmo Proxy de Terminaรงรฃo TLS para gerenciar a renovaรงรฃo de certificados ser tรฃo รบtil.
+ * Caso contrรกrio, vocรช pode ter que parar a execuรงรฃo do Proxy de Terminaรงรฃo TLS momentaneamente, inicializar o programa de renovaรงรฃo para renovar os certificados, e entรฃo reiniciar o Proxy de Terminaรงรฃo TLS. Isso nรฃo รฉ o ideal, jรก que sua(s) aplicaรงรฃo(รตes) nรฃo vรฃo estar disponรญveis enquanto o Proxy de Terminaรงรฃo TLS estiver desligado.
+
+Todo esse processo de renovaรงรฃo, enquanto o aplicativo ainda funciona, รฉ uma das principais razรตes para preferir um **sistema separado para gerenciar HTTPS** com um Proxy de Terminaรงรฃo TLS em vez de usar os certificados TLS no servidor da aplicaรงรฃo diretamente (e.g. com o Uvicorn).
+
+## Recapitulando
+
+Possuir **HTTPS** habilitado na sua aplicaรงรฃo รฉ bastante importante, e atรฉ **crรญtico** na maioria dos casos. A maior parte do esforรงo que vocรช tem que colocar sobre o HTTPS como desenvolvedor estรก em **entender esses conceitos** e como eles funcionam.
+
+Mas uma vez que vocรช saiba o bรกsico de **HTTPS para desenvolvedores**, vocรช pode combinar e configurar diferentes ferramentas facilmente para gerenciar tudo de uma forma simples.
+
+Em alguns dos prรณximos capรญtulos, eu mostrarei para vocรช vรกrios exemplos concretos de como configurar o **HTTPS** para aplicaรงรตes **FastAPI**. ๐
diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md
index bc23114dc..138048f06 100644
--- a/docs/pt/docs/index.md
+++ b/docs/pt/docs/index.md
@@ -11,15 +11,18 @@
Framework FastAPI, alta performance, fรกcil de aprender, fรกcil de codar, pronto para produรงรฃo
-
-
+
+
-
-
+
+
+
+
+
---
@@ -60,7 +63,7 @@ Os recursos chave sรฃo:
-
Outros patrocinadores
+
Outros patrocinadores
## Opiniรตes
@@ -70,6 +73,18 @@ Os recursos chave sรฃo:
---
+"_Nรณs adotamos a biblioteca **FastAPI** para iniciar um servidor **REST** que pode ser consultado para obter **previsรตes**. [para o Ludwig]_"
+
+
Piero Molino, Yaroslav Dudin, e Sai Sumanth Miryala -
Uber (ref)
+
+---
+
+"_A **Netflix** tem o prazer de anunciar o lanรงamento open-source do nosso framework de orquestraรงรฃo de **gerenciamento de crises**: **Dispatch**! [criado com **FastAPI**]_"
+
+
Kevin Glisson, Marc Vilanova, Forest Monsen -
Netflix (ref)
+
+---
+
"*Estou extremamente entusiasmado com o **FastAPI**. ร tรฃo divertido!*"
@@ -90,9 +105,9 @@ Os recursos chave sรฃo:
---
-"*Nรณs adotamos a biblioteca **FastAPI** para criar um servidor **REST** que possa ser chamado para obter **prediรงรตes**. [para o Ludwig]*"
+"_Se alguรฉm estiver procurando construir uma API Python para produรงรฃo, eu recomendaria fortemente o **FastAPI**. Ele รฉ **lindamente projetado**, **simples de usar** e **altamente escalรกvel**. Ele se tornou um **componente chave** para a nossa estratรฉgia API first de desenvolvimento e estรก impulsionando diversas automaรงรตes e serviรงos, como o nosso Virtual TAC Engineer._"
-
Piero Molino, Yaroslav Dudin e Sai Sumanth Miryala -
Uber (ref)
+
Deon Pillsbury -
Cisco (ref)
---
@@ -113,28 +128,20 @@ FastAPI estรก nos ombros de gigantes:
## Instalaรงรฃo
-
-
-```console
-$ pip install fastapi
-
----> 100%
-```
-
-
-
-Vocรช tambรฉm precisarรก de um servidor ASGI para produรงรฃo, tal como
Uvicorn ou
Hypercorn.
+Crie e ative um
ambiente virtual, e entรฃo instale o FastAPI:
```console
-$ pip install "uvicorn[standard]"
+$ pip install "fastapi[standard]"
---> 100%
```
+**Nota**: Certifique-se de que vocรช colocou `"fastapi[standard]"` com aspas, para garantir que funcione em todos os terminais.
+
## Exemplo
### Crie
@@ -184,7 +191,7 @@ async def read_item(item_id: int, q: Union[str, None] = None):
**Nota**:
-Se vocรช nรฃo sabe, verifique a seรงรฃo _"In a hurry?"_ sobre
`async` e `await` nas docs.
+Se vocรช nรฃo sabe, verifique a seรงรฃo _"Com pressa?"_ sobre
`async` e `await` nas docs.
@@ -195,11 +202,24 @@ Rode o servidor com:
```console
-$ uvicorn main:app --reload
-
+$ fastapi dev main.py
+
+ โญโโโโโโโโโโ FastAPI CLI - Development mode โโโโโโโโโโโโฎ
+ โ โ
+ โ Serving at: http://127.0.0.1:8000 โ
+ โ โ
+ โ API docs: http://127.0.0.1:8000/docs โ
+ โ โ
+ โ Running in development mode, for production use: โ
+ โ โ
+ โ fastapi run โ
+ โ โ
+ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
+
+INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
-INFO: Started reloader process [28720]
-INFO: Started server process [28722]
+INFO: Started reloader process [2248755] using WatchFiles
+INFO: Started server process [2248757]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
@@ -207,13 +227,13 @@ INFO: Application startup complete.
-Sobre o comando uvicorn main:app --reload
...
+Sobre o comando fastapi dev main.py
...
-O comando `uvicorn main:app` se refere a:
+O comando `fastapi dev` lรช o seu arquivo `main.py`, identifica o aplicativo **FastAPI** nele, e inicia um servidor usando o Uvicorn.
-* `main`: o arquivo `main.py` (o "mรณdulo" Python).
-* `app`: o objeto criado dentro de `main.py` com a linha `app = FastAPI()`.
-* `--reload`: faz o servidor recarregar apรณs mudanรงas de cรณdigo. Somente faรงa isso para desenvolvimento.
+Por padrรฃo, o `fastapi dev` iniciarรก com *auto-reload* habilitado para desenvolvimento local.
+
+Vocรช pode ler mais sobre isso na documentaรงรฃo do FastAPI CLI.
@@ -268,7 +288,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: float
- is_offer: Union[bool] = None
+ is_offer: Union[bool, None] = None
@app.get("/")
@@ -286,7 +306,7 @@ def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
-O servidor deverรก recarregar automaticamente (porquรช vocรช adicionou `--reload` ao comando `uvicorn` acima).
+O servidor `fastapi dev` deverรก recarregar automaticamente.
### Evoluindo a Documentaรงรฃo Interativa da API
@@ -316,7 +336,7 @@ E agora, vรก para
Tutorial - Guia do Usuรกrio.
+Para um exemplo mais completo incluindo mais recursos, veja
Tutorial - Guia do Usuรกrio.
**Alerta de Spoiler**: o tutorial - guia do usuรกrio inclui:
@@ -416,9 +436,9 @@ Para um exemplo mais completo incluindo mais recursos, veja
Injeรงรฃo de Dependรชncia**.
* Seguranรงa e autenticaรงรฃo, incluindo suporte para **OAuth2** com autenticaรงรฃo **JWT tokens** e **HTTP Basic**.
* Tรฉcnicas mais avanรงadas (mas igualmente fรกceis) para declaraรงรฃo de **modelos JSON profundamente aninhados** (graรงas ao Pydantic).
+* Integraรงรตes **GraphQL** com o Strawberry e outras bibliotecas.
* Muitos recursos extras (graรงas ao Starlette) como:
* **WebSockets**
- * **GraphQL**
* testes extrememamente fรกceis baseados em HTTPX e `pytest`
* **CORS**
* **Cookie Sessions**
@@ -428,30 +448,49 @@ Para um exemplo mais completo incluindo mais recursos, veja
um dos _frameworks_ Python mais rรกpidos disponรญveis, somente atrรกs de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*)
-Para entender mais sobre performance, veja a seรงรฃo
Benchmarks.
+Para entender mais sobre performance, veja a seรงรฃo
Comparaรงรตes.
+
+## Dependรชncias
+
+O FastAPI depende do Pydantic e do Starlette.
+
-## Dependรชncias opcionais
+### Dependรชncias `standard`
-Usados por Pydantic:
+Quando vocรช instala o FastAPI com `pip install "fastapi[standard]"`, ele vรชm com o grupo `standard` (padrรฃo) de dependรชncias opcionais:
+
+Utilizado pelo Pydantic:
*
email-validator
- para validaรงรฃo de email.
-Usados por Starlette:
+Utilizado pelo Starlette:
+
+*
httpx
- Obrigatรณrio caso vocรช queira utilizar o `TestClient`.
+*
jinja2
- Obrigatรณrio se vocรช quer utilizar a configuraรงรฃo padrรฃo de templates.
+*
python-multipart
- Obrigatรณrio se vocรช deseja suporte a
"parsing" de formulรกrio, com `request.form()`.
+
+Utilizado pelo FastAPI / Starlette:
+
+*
uvicorn
- para o servidor que carrega e serve a sua aplicaรงรฃo. Isto inclui `uvicorn[standard]`, que inclui algumas dependรชncias (e.g. `uvloop`) necessรกrias para servir em alta performance.
+* `fastapi-cli` - que disponibiliza o comando `fastapi`.
+
+### Sem as dependรชncias `standard`
+
+Se vocรช nรฃo deseja incluir as dependรชncias opcionais `standard`, vocรช pode instalar utilizando `pip install fastapi` ao invรฉs de `pip install "fastapi[standard]"`.
+
+### Dpendรชncias opcionais adicionais
+
+Existem algumas dependรชncias adicionais que vocรช pode querer instalar.
-*
httpx
- Necessรกrio se vocรช quiser utilizar o `TestClient`.
-*
jinja2
- Necessรกrio se vocรช quiser utilizar a configuraรงรฃo padrรฃo de templates.
-*
python-multipart
- Necessรกrio se vocรช quiser suporte com
"parsing" de formulรกrio, com `request.form()`.
-*
itsdangerous
- Necessรกrio para suporte a `SessionMiddleware`.
-*
pyyaml
- Necessรกrio para suporte a `SchemaGenerator` da Starlette (vocรช provavelmente nรฃo precisarรก disso com o FastAPI).
-*
graphene
- Necessรกrio para suporte a `GraphQLApp`.
+Dependรชncias opcionais adicionais do Pydantic:
-Usados por FastAPI / Starlette:
+*
pydantic-settings
- para gerenciamento de configuraรงรตes.
+*
pydantic-extra-types
- tipos extras para serem utilizados com o Pydantic.
-*
uvicorn
- para o servidor que carrega e serve sua aplicaรงรฃo.
-*
orjson
- Necessรกrio se vocรช quer utilizar `ORJSONResponse`.
-*
ujson
- Necessรกrio se vocรช quer utilizar `UJSONResponse`.
+Dependรชncias opcionais adicionais do FastAPI:
-Vocรช pode instalar todas essas dependรชncias com `pip install fastapi[all]`.
+*
orjson
- Obrigatรณrio se vocรช deseja utilizar o `ORJSONResponse`.
+*
ujson
- Obrigatรณrio se vocรช deseja utilizar o `UJSONResponse`.
## Licenรงa
diff --git a/docs/pt/docs/tutorial/request-forms.md b/docs/pt/docs/tutorial/request-forms.md
index 756ceb581..572ddf003 100644
--- a/docs/pt/docs/tutorial/request-forms.md
+++ b/docs/pt/docs/tutorial/request-forms.md
@@ -6,7 +6,11 @@ Quando vocรช precisar receber campos de formulรกrio ao invรฉs de JSON, vocรช pod
Para usar formulรกrios, primeiro instale
`python-multipart`.
-Ex: `pip install python-multipart`.
+Lembre-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativรก-lo e entรฃo instalar a dependรชncia, por exemplo:
+
+```console
+$ pip install python-multipart
+```
///
diff --git a/docs/ru/docs/advanced/async-tests.md b/docs/ru/docs/advanced/async-tests.md
new file mode 100644
index 000000000..7849ad109
--- /dev/null
+++ b/docs/ru/docs/advanced/async-tests.md
@@ -0,0 +1,99 @@
+# ะัะธะฝั
ัะพะฝะฝะพะต ัะตััะธัะพะฒะฐะฝะธะต
+
+ะั ัะถะต ะฒะธะดะตะปะธ ะบะฐะบ ัะตััะธัะพะฒะฐัั **FastAPI** ะฟัะธะปะพะถะตะฝะธะต, ะธัะฟะพะปัะทัั ะธะผะตััะธะนัั ะบะปะฐัั `TestClient`. ะ ััะพะผั ะผะพะผะตะฝัั ะฒั ะฒะธะดะตะปะธ ัะพะปัะบะพ ะบะฐะบ ะฟะธัะฐัั ัะตััั ะฒ ัะธะฝั
ัะพะฝะฝะพะผ ััะธะปะต ะฑะตะท ะธัะฟะพะปัะทะพะฒะฐะฝะธั `async` ััะฝะบัะธะน.
+
+ะะพะทะผะพะถะฝะพััั ะธัะฟะพะปัะทะพะฒะฐะฝะธั ะฐัะธะฝั
ัะพะฝะฝัั
ััะฝะบัะธะน ะฒ ะฒะฐัะธั
ัะตััะฐั
ะผะพะถะตั ะฑััั ะฟะพะปะตะทะฝa, ะบะพะณะดะฐ, ะฝะฐะฟัะธะผะตั, ะฒั ะฐัะธะฝั
ัะพะฝะฝะพ ะพะฑัะฐัะฐะตัะตัั ะบ ะฒะฐัะตะน ะฑะฐะทะต ะดะฐะฝะฝัั
. ะัะตะดััะฐะฒััะต, ััะพ ะฒั ั
ะพัะธัะต ะพัะฟัะฐะฒะธัั ะทะฐะฟัะพัั ะฒ ะฒะฐัะต FastAPI ะฟัะธะปะพะถะตะฝะธะต, ะฐ ะทะฐัะตะผ ะฟัะธ ะฟะพะผะพัะธ ะฐัะธะฝั
ัะพะฝะฝะพะน ะฑะธะฑะปะธะพัะตะบะธ ะดะปั ัะฐะฑะพัั ั ะฑะฐะทะพะน ะดะฐะฝะฝัั
ัะดะพััะพะฒะตัะธัััั, ััะพ ะฒะฐั ะฑะตะบัะฝะด ะบะพััะตะบัะฝะพ ะทะฐะฟะธัะฐะป ะดะฐะฝะฝัะต ะฒ ะฑะฐะทั ะดะฐะฝะฝัั
.
+
+ะะฐะฒะฐะนัะต ัะฐััะผะพััะธะผ, ะบะฐะบ ะผั ะผะพะถะตะผ ััะพ ัะตะฐะปะธะทะพะฒะฐัั.
+
+## pytest.mark.anyio
+
+ะัะปะธ ะผั ั
ะพัะธะผ ะฒัะทัะฒะฐัั ะฐัะธะฝั
ัะพะฝะฝัะต ััะฝะบัะธะธ ะฒ ะฝะฐัะธั
ัะตััะฐั
, ัะพ ะฝะฐัะธ ัะตััะพะฒัะต ััะฝะบัะธะธ ะดะพะปะถะฝั ะฑััั ะฐัะธะฝั
ัะพะฝะฝัะผะธ. AnyIO ะฟัะตะดะพััะฐะฒะปัะตั ะดะปั ััะพะณะพ ะพัะปะธัะฝัะน ะฟะปะฐะณะธะฝ, ะบะพัะพััะน ะฟะพะทะฒะพะปัะตั ะฝะฐะผ ัะบะฐะทัะฒะฐัั, ะบะฐะบะธะต ัะตััะพะฒัะต ััะฝะบัะธะธ ะดะพะปะถะฝั ะฒัะทัะฒะฐัััั ะฐัะธะฝั
ัะพะฝะฝะพ.
+
+## HTTPX
+
+ะะฐะถะต ะตัะปะธ **FastAPI** ะฟัะธะปะพะถะตะฝะธะต ะธัะฟะพะปัะทัะตั ะพะฑััะฝัะต ััะฝะบัะธะธ `def` ะฒะผะตััะพ `async def`, ััะพ ะฒัะต ัะฐะฒะฝะพ `async` ะฟัะธะปะพะถะตะฝะธะต 'ะฟะพะด ะบะฐะฟะพัะพะผ'.
+
+ะงัะพะฑั ัะฐะฑะพัะฐัั ั ะฐัะธะฝั
ัะพะฝะฝัะผ FastAPI ะฟัะธะปะพะถะตะฝะธะตะผ ะฒ ะฒะฐัะธั
ะพะฑััะฝัั
ัะตััะพะฒัั
ััะฝะบัะธัั
`def`, ะธัะฟะพะปัะทัั ััะฐะฝะดะฐััะฝัะน pytest, `TestClient` ะฒะฝัััะธ ัะตะฑั ะดะตะปะฐะตั ะฝะตะบะพัะพััั ะผะฐะณะธั. ะะพ ััะฐ ะผะฐะณะธั ะฟะตัะตััะฐะตั ัะฐะฑะพัะฐัั, ะบะพะณะดะฐ ะผั ะธัะฟะพะปัะทัะตะผ ะตะณะพ ะฒะฝัััะธ ะฐัะธะฝั
ัะพะฝะฝัั
ััะฝะบัะธะน. ะะฐะฟััะบะฐั ะฝะฐัะธ ัะตััั ะฐัะธะฝั
ัะพะฝะฝะพ, ะผั ะฑะพะปััะต ะฝะต ะผะพะถะตะผ ะธัะฟะพะปัะทะพะฒะฐัั `TestClient` ะฒะฝัััะธ ะฝะฐัะธั
ัะตััะพะฒัั
ััะฝะบัะธะน.
+
+`TestClient` ะพัะฝะพะฒะฐะฝ ะฝะฐ
HTTPX, ะธ, ะบ ััะฐัััั, ะผั ะผะพะถะตะผ ะธัะฟะพะปัะทะพะฒะฐัั ะตะณะพ (`HTTPX`) ะฝะฐะฟััะผัั ะดะปั ัะตััะธัะพะฒะฐะฝะธั API.
+
+## ะัะธะผะตั
+
+ะ ะบะฐัะตััะฒะต ะฟัะพััะพะณะพ ะฟัะธะผะตัะฐ, ะดะฐะฒะฐะนัะต ัะฐััะผะพััะธะผ ัะฐะนะปะพะฒัั ััััะบัััั, ัั
ะพะถัั ั ะพะฟะธัะฐะฝะฝะพะน ะฒ [ะะพะปััะธะต ะฟัะธะปะพะถะตะฝะธั](../tutorial/bigger-applications.md){.internal-link target=_blank} ะธ [ะขะตััะธัะพะฒะฐะฝะธะต](../tutorial/testing.md){.internal-link target=_blank}:
+
+```
+.
+โโโ app
+โย ย โโโ __init__.py
+โย ย โโโ main.py
+โย ย โโโ test_main.py
+```
+
+ะคะฐะนะป `main.py`:
+
+{* ../../docs_src/async_tests/main.py *}
+
+ะคะฐะนะป `test_main.py` ัะพะดะตัะถะธั ัะตััั ะดะปั `main.py`, ัะตะฟะตัั ะพะฝ ะผะพะถะตั ะฒัะณะปัะดะตัั ัะฐะบ:
+
+{* ../../docs_src/async_tests/test_main.py *}
+
+## ะะฐะฟััะบ ัะตััะพะฒ
+
+ะั ะผะพะถะตัะต ะทะฐะฟัััะธัั ัะฒะพะธ ัะตััั ะบะฐะบ ะพะฑััะฝะพ:
+
+
+
+```console
+$ pytest
+
+---> 100%
+```
+
+
+
+## ะะพะดัะพะฑะฝะตะต
+
+ะะฐัะบะตั `@pytest.mark.anyio` ะณะพะฒะพัะธั pytest, ััะพ ัะตััะพะฒะฐั ััะฝะบัะธั ะดะพะปะถะฝะฐ ะฑััั ะฒัะทะฒะฐะฝะฐ ะฐัะธะฝั
ัะพะฝะฝะพ:
+
+{* ../../docs_src/async_tests/test_main.py hl[7] *}
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะฑัะฐัะธัะต ะฒะฝะธะผะฐะฝะธะต, ััะพ ัะตััะพะฒะฐั ััะฝะบัะธั ัะตะฟะตัั `async def` ะฒะผะตััะพ ะฟัะพััะพะณะพ `def`, ะบะฐะบ ััะพ ะฑัะปะพ ะฟัะธ ะธัะฟะพะปัะทะพะฒะฐะฝะธะธ `TestClient`.
+
+///
+
+ะะฐัะตะผ ะผั ะผะพะถะตะผ ัะพะทะดะฐัั `AsyncClient` ัะพ ัััะปะบะพะน ะฝะฐ ะฟัะธะปะพะถะตะฝะธะต ะธ ะฟะพััะปะฐัั ะฐัะธะฝั
ัะพะฝะฝัะต ะทะฐะฟัะพัั, ะธัะฟะพะปัะทัั `await`.
+
+{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
+
+ะญัะพ ัะบะฒะธะฒะฐะปะตะฝัะฝะพ ัะปะตะดัััะตะผั:
+
+```Python
+response = client.get('/')
+```
+
+...ะบะพัะพัะพะต ะผั ะธัะฟะพะปัะทะพะฒะฐะปะธ ะดะปั ะพัะฟัะฐะฒะบะธ ะฝะฐัะธั
ะทะฐะฟัะพัะพะฒ ั `TestClient`.
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะฑัะฐัะธัะต ะฒะฝะธะผะฐะฝะธะต, ััะพ ะผั ะธัะฟะพะปัะทัะตะผ async/await ั `AsyncClient` - ะทะฐะฟัะพั ะฐัะธะฝั
ัะพะฝะฝัะน.
+
+///
+
+/// warning | ะะฝะธะผะฐะฝะธะต
+
+ะัะปะธ ะฒะฐัะต ะฟัะธะปะพะถะตะฝะธะต ะฟะพะปะฐะณะฐะตััั ะฝะฐ lifespan ัะพะฑััะธั, ัะพ `AsyncClient` ะฝะต ะทะฐะฟัััะธั ััะธ ัะพะฑััะธั. ะงัะพะฑั ะพะฑะตัะฟะตัะธัั ะธั
ััะฐะฑะฐััะฒะฐะฝะธะต ะธัะฟะพะปัะทัะนัะต `LifespanManager` ะธะท
florimondmanca/asgi-lifespan.
+
+///
+
+## ะัะทะพะฒ ะดััะณะธั
ะฐัะธะฝั
ัะพะฝะฝัั
ััะฝะบัะธะน
+
+ะขะตะฟะตัั ัะตััะพะฒะฐั ััะฝะบัะธั ััะฐะปะฐ ะฐัะธะฝั
ัะพะฝะฝะพะน, ะฟะพััะพะผั ะฒะฝัััะธ ะฝะตะต ะฒั ะผะพะถะตัะต ะฒัะทัะฒะฐัั ัะฐะบะถะต ะธ ะดััะณะธะต `async` ััะฝะบัะธะธ, ะฝะต ัะฒัะทะฐะฝะฝัะต ั ะพัะฟัะฐะฒะปะตะฝะธะตะผ ะทะฐะฟัะพัะพะฒ ะฒ ะฒะฐัะต FastAPI ะฟัะธะปะพะถะตะฝะธะต. ะะฐะบ ะตัะปะธ ะฑั ะฒั ะฒัะทัะฒะฐะปะธ ะธั
ะฒ ะปัะฑะพะผ ะดััะณะพะผ ะผะตััะต ะฒะฐัะตะณะพ ะบะพะดะฐ.
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะัะปะธ ะฒั ััะพะปะบะฝัะปะธัั ั `RuntimeError: Task attached to a different loop` ะฟัะธ ะฒัะทะพะฒะต ะฐัะธะฝั
ัะพะฝะฝัั
ััะฝะบัะธะน ะฒ ะฒะฐัะธั
ัะตััะฐั
(ะฝะฐะฟัะธะผะตั, ะฟัะธ ะธัะฟะพะปัะทะพะฒะฐะฝะธะธ
MongoDB's MotorClient), ัะพ ะฝะต ะทะฐะฑัะฒะฐะนัะต ะธะฝะธัะธะฐะปะธะทะธัะพะฒะฐัั ะพะฑัะตะบัั, ะบะพัะพััะผ ะฝัะถะตะฝ ัะธะบะป ัะพะฑััะธะน (event loop), ัะพะปัะบะพ ะฒะฝัััะธ ะฐัะธะฝั
ัะพะฝะฝัั
ััะฝะบัะธะน, ะฝะฐะฟัะธะผะตั, ะฒ `'@app.on_event("startup")` callback.
+
+///
diff --git a/docs/ru/docs/advanced/response-cookies.md b/docs/ru/docs/advanced/response-cookies.md
new file mode 100644
index 000000000..e04ff577c
--- /dev/null
+++ b/docs/ru/docs/advanced/response-cookies.md
@@ -0,0 +1,48 @@
+
+# Cookies ะฒ ะพัะฒะตัะต
+
+## ะัะฟะพะปัะทะพะฒะฐะฝะธะต ะฟะฐัะฐะผะตััะฐ `Response`
+
+ะั ะผะพะถะตัะต ะพะฑััะฒะธัั ะฟะฐัะฐะผะตัั ัะธะฟะฐ `Response` ะฒ ะฒะฐัะตะน ััะฝะบัะธะธ ัะฝะดะฟะพะธะฝัะฐ.
+
+ะะฐัะตะผ ัััะฐะฝะพะฒะธัั cookies ะฒ ััะพะผ ะฒัะตะผะตะฝะฝะพะผ ะพะฑัะตะบัะต ะพัะฒะตัะฐ.
+
+{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *}
+
+ะะพัะปะต ััะพะณะพ ะผะพะถะฝะพ ะฒะตัะฝััั ะปัะฑะพะน ะพะฑัะตะบั, ะบะฐะบ ะธ ัะฐะฝััะต (ะฝะฐะฟัะธะผะตั, `dict`, ะพะฑัะตะบั ะผะพะดะตะปะธ ะฑะฐะทั ะดะฐะฝะฝัั
ะธ ัะฐะบ ะดะฐะปะตะต).
+
+ะัะปะธ ะฒั ัะบะฐะทะฐะปะธ `response_model`, ะพะฝ ะฒัั ัะฐะฒะฝะพ ะฑัะดะตั ะธัะฟะพะปัะทะพะฒะฐัััั ะดะปั ัะธะปัััะฐัะธะธ ะธ ะฟัะตะพะฑัะฐะทะพะฒะฐะฝะธั ะฒะพะทะฒัะฐัะฐะตะผะพะณะพ ะพะฑัะตะบัะฐ.
+
+**FastAPI** ะธะทะฒะปะตัะตั cookies (ะฐ ัะฐะบะถะต ะทะฐะณะพะปะพะฒะบะธ ะธ ะบะพะดั ัะพััะพัะฝะธั) ะธะท ะฒัะตะผะตะฝะฝะพะณะพ ะพัะฒะตัะฐ ะธ ะฒะบะปััะธั ะธั
ะฒ ะพะบะพะฝัะฐัะตะปัะฝัะน ะพัะฒะตั, ัะพะดะตัะถะฐัะธะน ะฒะฐัะต ะฒะพะทะฒัะฐัะฐะตะผะพะต ะทะฝะฐัะตะฝะธะต, ะพััะธะปัััะพะฒะฐะฝะฝะพะต ัะตัะตะท `response_model`.
+
+ะั ัะฐะบะถะต ะผะพะถะตัะต ะพะฑััะฒะธัั ะฟะฐัะฐะผะตัั ัะธะฟะฐ Response ะฒ ะทะฐะฒะธัะธะผะพัััั
ะธ ัััะฐะฝะฐะฒะปะธะฒะฐัั cookies (ะธ ะทะฐะณะพะปะพะฒะบะธ) ัะฐะผ.
+
+## ะะพะทะฒัะฐัะตะฝะธะต `Response` ะฝะฐะฟััะผัั
+
+ะั ัะฐะบะถะต ะผะพะถะตัะต ัััะฐะฝะพะฒะธัั cookies, ะตัะปะธ ะฒะพะทะฒัะฐัะฐะตัะต `Response` ะฝะฐะฟััะผัั ะฒ ะฒะฐัะตะผ ะบะพะดะต.
+
+ะะปั ััะพะณะพ ัะพะทะดะฐะนัะต ะพะฑัะตะบั `Response`, ะบะฐะบ ะพะฟะธัะฐะฝะพ ะฒ ัะฐะทะดะตะปะต [ะะพะทะฒัะฐัะตะฝะธะต ะพัะฒะตัะฐ ะฝะฐะฟััะผัั](response-directly.md){.target=_blank}.
+
+ะะฐัะตะผ ัััะฐะฝะพะฒะธัะต cookies ะธ ะฒะตัะฝะธัะต ััะพั ะพะฑัะตะบั:
+
+{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
+
+/// tip | ะัะธะผะตัะฐะฝะธะต
+ะะผะตะนัะต ะฒ ะฒะธะดั, ััะพ ะตัะปะธ ะฒั ะฒะพะทะฒัะฐัะฐะตัะต ะพัะฒะตั ะฝะฐะฟััะผัั, ะฒะผะตััะพ ะธัะฟะพะปัะทะพะฒะฐะฝะธั ะฟะฐัะฐะผะตััะฐ `Response`, **FastAPI** ะพัะฟัะฐะฒะธั ะตะณะพ ะฑะตะท ะดะพะฟะพะปะฝะธัะตะปัะฝะพะน ะพะฑัะฐะฑะพัะบะธ.
+
+ะฃะฑะตะดะธัะตัั, ััะพ ะฒะฐัะธ ะดะฐะฝะฝัะต ะธะผะตัั ะบะพััะตะบัะฝัะน ัะธะฟ. ะะฐะฟัะธะผะตั, ะพะฝะธ ะดะพะปะถะฝั ะฑััั ัะพะฒะผะตััะธะผั ั JSON, ะตัะปะธ ะฒั ะธัะฟะพะปัะทัะตัะต `JSONResponse`.
+
+ะขะฐะบะถะต ัะฑะตะดะธัะตัั, ััะพ ะฒั ะฝะต ะพัะฟัะฐะฒะปัะตัะต ะดะฐะฝะฝัะต, ะบะพัะพััะต ะดะพะปะถะฝั ะฑัะปะธ ะฑััั ะพััะธะปัััะพะฒะฐะฝั ัะตัะตะท `response_model`.
+///
+
+### ะะพะฟะพะปะฝะธัะตะปัะฝะฐั ะธะฝัะพัะผะฐัะธั
+
+/// note | ะขะตั
ะฝะธัะตัะบะธะต ะดะตัะฐะปะธ
+ะั ัะฐะบะถะต ะผะพะถะตัะต ะธัะฟะพะปัะทะพะฒะฐัั `from starlette.responses import Response` ะธะปะธ `from starlette.responses import JSONResponse`.
+
+**FastAPI** ะฟัะตะดะพััะฐะฒะปัะตั `fastapi.responses`, ะบะพัะพััะต ัะฒะปััััั ัะตะผะธ ะถะต ะพะฑัะตะบัะฐะผะธ, ััะพ ะธ `starlette.responses`, ะฟัะพััะพ ะดะปั ัะดะพะฑััะฒะฐ. ะะดะฝะฐะบะพ ะฑะพะปััะธะฝััะฒะพ ะดะพัััะฟะฝัั
ัะธะฟะพะฒ ะพัะฒะตัะพะฒ ะฟะพัััะฟะฐะตั ะฝะตะฟะพััะตะดััะฒะตะฝะฝะพ ะธะท **Starlette**.
+
+ะะปั ัััะฐะฝะพะฒะบะธ ะทะฐะณะพะปะพะฒะบะพะฒ ะธ cookies `Response` ะธัะฟะพะปัะทัะตััั ัะฐััะพ, ะฟะพััะพะผั **FastAPI** ัะฐะบะถะต ะฟัะตะดะพััะฐะฒะปัะตั ะตะณะพ ัะตัะตะท `fastapi.responses`.
+///
+
+ะงัะพะฑั ัะฒะธะดะตัั ะฒัะต ะดะพัััะฟะฝัะต ะฟะฐัะฐะผะตััั ะธ ะฝะฐัััะพะนะบะธ, ะพะทะฝะฐะบะพะผััะตัั ั
ะดะพะบัะผะตะฝัะฐัะธะตะน Starlette.
diff --git a/docs/ru/docs/advanced/websockets.md b/docs/ru/docs/advanced/websockets.md
new file mode 100644
index 000000000..bc9dfcbff
--- /dev/null
+++ b/docs/ru/docs/advanced/websockets.md
@@ -0,0 +1,186 @@
+# ะะตะฑ-ัะพะบะตัั
+
+ะั ะผะพะถะตัะต ะธัะฟะพะปัะทะพะฒะฐัั
ะฒะตะฑ-ัะพะบะตัั ะฒ **FastAPI**.
+
+## ะฃััะฐะฝะพะฒะบะฐ `WebSockets`
+
+ะฃะฑะตะดะธัะตัั, ััะพ [ะฒะธัััะฐะปัะฝะฐั ััะตะดะฐ](../virtual-environments.md){.internal-link target=_blank} ัะพะทะดะฐะฝะฐ, ะฐะบัะธะฒะธััะนัะต ะตั ะธ ัััะฐะฝะพะฒะธัะต `websockets`:
+
+
+
+```console
+$ pip install websockets
+
+---> 100%
+```
+
+
+
+## ะะปะธะตะฝั WebSockets
+
+### ะ ะฐะฑะพัะตะต ะฟัะธะปะพะถะตะฝะธะต
+
+ะกะบะพัะตะต ะฒัะตะณะพ, ะฒ ะฒะฐัะตะน ัะตะฐะปัะฝะพะน ะฟัะพะดัะบัะพะฒะพะน ัะธััะตะผะต ะตััั ััะพะฝัะตะฝะด, ัะตะฐะปะธะทะพะฒะฐะฝะฝัะน ะฟัะธ ะฟะพะผะพัะธ ัะพะฒัะตะผะตะฝะฝัั
ััะตะนะผะฒะพัะบะพะฒ React, Vue.js ะธะปะธ Angular.
+
+ะ ะฝะฐะฒะตัะฝัะบะฐ ะดะปั ะฒะทะฐะธะผะพะดะตะนััะฒะธั ั ะฑะตะบะตะฝะดะพะผ ัะตัะตะท ะฒะตะฑ-ัะพะบะตัั ะฒั ะฑัะดะตัะต ะธัะฟะพะปัะทะพะฒะฐัั ััะตะดััะฒะฐ ััะพะฝัะตะฝะดะฐ.
+
+ะขะฐะบะถะต ั ะฒะฐั ะผะพะถะตั ะฑััั ะฝะฐัะธะฒะฝะพะต ะผะพะฑะธะปัะฝะพะต ะฟัะธะปะพะถะตะฝะธะต, ะบะพะผะผัะฝะธัะธััััะตะต ะฝะตะฟะพััะตะดััะฒะตะฝะฝะพ ั ะฒะตะฑ-ัะพะบะตัะฐะผะธ ะฝะฐ ะฑะตะบะตะฝะด-ัะตัะฒะตัะต.
+
+ะะธะฑะพ ะฒั ะผะพะถะตัะต ัะดะตะปะฐัั ะบะฐะบะพะน-ะปะธะฑะพ ะดััะณะพะน ัะฟะพัะพะฑ ะฒะทะฐะธะผะพะดะตะนััะฒะธั ั ะฒะตะฑ-ัะพะบะตัะฐะผะธ.
+
+---
+
+ะะพ ะดะปั ััะพะณะพ ะฟัะธะผะตัะฐ ะผั ะฒะพัะฟะพะปัะทัะตะผัั ะพัะตะฝั ะฟัะพัััะผ HTML ะดะพะบัะผะตะฝัะพะผ ั ะฝะตะฑะพะปััะธะผะธ ะฒััะฐะฒะบะฐะผะธ JavaScript ะบะพะดะฐ.
+
+ะะพะฝะตัะฝะพ ะถะต ััะพ ะฝะตะพะฟัะธะผะฐะปัะฝะพ, ะธ ะฝะฐ ะฟัะฐะบัะธะบะต ัะฐะบ ะดะตะปะฐัั ะฝะต ััะพะธั.
+
+ะ ัะตะฐะปัะฝัั
ะฟัะธะปะพะถะตะฝะธัั
ััะพะธั ะฒะพัะฟะพะปัะทะพะฒะฐัััั ะพะดะฝะธะผ ะธะท ะฒััะตัะฟะพะผัะฝัััั
ัะฟะพัะพะฑะพะฒ.
+
+ะะปั ะฟัะธะผะตัะฐ ะฝะฐะผ ะฝัะถะตะฝ ะฝะฐะธะฑะพะปะตะต ะฟัะพััะพะน ัะฟะพัะพะฑ, ะบะพัะพััะน ะฟะพะทะฒะพะปะธั ัะพััะตะดะพัะพัะธัััั ะฝะฐ ัะตัะฒะตัะฝะพะน ัะฐััะธ ะฒะตะฑ-ัะพะบะตัะพะฒ ะธ ะฟะพะปััะธัั ัะฐะฑะพัะธะน ะบะพะด:
+
+{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
+
+## ะกะพะทะดะฐะฝะธะต `websocket`
+
+ะกะพะทะดะฐะนัะต `websocket` ะฒ ัะฒะพะตะผ **FastAPI** ะฟัะธะปะพะถะตะฝะธะธ:
+
+{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
+
+/// note | ะขะตั
ะฝะธัะตัะบะธะต ะดะตัะฐะปะธ
+
+ะั ัะฐะบะถะต ะผะพะถะตัะต ะธัะฟะพะปัะทะพะฒะฐัั `from starlette.websockets import WebSocket`.
+
+**FastAPI** ะฝะฐะฟััะผัั ะฟัะตะดะพััะฐะฒะปัะตั ัะพั ะถะต ัะฐะผัะน `WebSocket` ะฟัะพััะพ ะดะปั ัะดะพะฑััะฒะฐ. ะะฐ ัะฐะผะพะผ ะดะตะปะต ััะพ `WebSocket` ะธะท Starlette.
+
+///
+
+## ะะถะธะดะฐะฝะธะต ะธ ะพัะฟัะฐะฒะบะฐ ัะพะพะฑัะตะฝะธะน
+
+ะงะตัะตะท ัะฝะดะฟะพะธะฝั ะฒะตะฑ-ัะพะบะตัะฐ ะฒั ะผะพะถะตัะต ะฟะพะปััะฐัั ะธ ะพัะฟัะฐะฒะปััั ัะพะพะฑัะตะฝะธั.
+
+{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
+
+ะั ะผะพะถะตัะต ะฟะพะปััะฐัั ะธ ะพัะฟัะฐะฒะปััั ะดะฒะพะธัะฝัะต, ัะตะบััะพะฒัะต ะธ JSON ะดะฐะฝะฝัะต.
+
+## ะัะพะฒะตัะบะฐ ะฒ ะดะตะนััะฒะธะธ
+
+ะัะปะธ ะฒะฐั ัะฐะนะป ะฝะฐะทัะฒะฐะตััั `main.py`, ัะพ ะทะฐะฟัััะธัะต ะฟัะธะปะพะถะตะฝะธะต ะบะพะผะฐะฝะดะพะน:
+
+
+
+```console
+$ fastapi dev main.py
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+
+
+ะัะบัะพะนัะต ะฑัะฐัะทะตั ะฟะพ ะฐะดัะตัั
http://127.0.0.1:8000.
+
+ะั ัะฒะธะดะธัะต ัะปะตะดััััั ะฟัะพััะตะฝัะบัั ัััะฐะฝะธัั:
+
+

+
+ะั ะผะพะถะตัะต ะฝะฐะฑะธัะฐัั ัะพะพะฑัะตะฝะธั ะฒ ะฟะพะปะต ะฒะฒะพะดะฐ ะธ ะพัะฟัะฐะฒะปััั ะธั
:
+
+

+
+ะ ะฒะฐัะต **FastAPI** ะฟัะธะปะพะถะตะฝะธะต ั ะฒะตะฑ-ัะพะบะตัะฐะผะธ ะพัะฒะตัะธั:
+
+

+
+ะั ะผะพะถะตัะต ะพัะฟัะฐะฒะปััั ะธ ะฟะพะปััะฐัั ะผะฝะพะถะตััะฒะพ ัะพะพะฑัะตะฝะธะน:
+
+

+
+ะ ะฒัะต ะพะฝะธ ะฑัะดัั ะธัะฟะพะปัะทะพะฒะฐัั ะพะดะฝะพ ะธ ัะพ ะถะต ะฒะตะฑ-ัะพะบะตั ัะพะตะดะธะฝะตะฝะธะต.
+
+## ะัะฟะพะปัะทะพะฒะฐะฝะธะต `Depends` ะธ ะฝะต ัะพะปัะบะพ
+
+ะั ะผะพะถะตัะต ะธะผะฟะพััะธัะพะฒะฐัั ะธะท `fastapi` ะธ ะธัะฟะพะปัะทะพะฒะฐัั ะฒ ัะฝะดะฟะพะธะฝัะต ะฒะตะฑัะพะบะตัะฐ:
+
+* `Depends`
+* `Security`
+* `Cookie`
+* `Header`
+* `Path`
+* `Query`
+
+ะะฝะธ ัะฐะฑะพัะฐัั ัะฐะบ ะถะต, ะบะฐะบ ะธ ะฒ ะดััะณะธั
FastAPI ัะฝะดะฟะพะธะฝัะฐั
/*ะพะฟะตัะฐัะธัั
ะฟััะธ*:
+
+{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
+
+/// info | ะัะธะผะตัะฐะฝะธะต
+
+ะ ะฒะตะฑ-ัะพะบะตัะต ะฒัะทัะฒะฐัั `HTTPException` ะฝะต ะธะผะตะตั ัะผััะปะฐ. ะะผะตััะพ ััะพะณะพ ะฝัะถะฝะพ ะธัะฟะพะปัะทะพะฒะฐัั `WebSocketException`.
+
+ะะฐะบััะฒะฐััะธะน ััะฐััั ะบะพะด ะผะพะถะฝะพ ะธัะฟะพะปัะทะพะฒะฐัั ะธะท
valid codes defined in the specification.
+
+///
+
+### ะะตะฑ-ัะพะบะตัั ั ะทะฐะฒะธัะธะผะพัััะผะธ: ะฟัะพะฒะตัะบะฐ ะฒ ะดะตะนััะฒะธะธ
+
+ะัะปะธ ะฒะฐั ัะฐะนะป ะฝะฐะทัะฒะฐะตััั `main.py`, ัะพ ะทะฐะฟัััะธัะต ะฟัะธะปะพะถะตะฝะธะต ะบะพะผะฐะฝะดะพะน:
+
+
+
+```console
+$ fastapi dev main.py
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+
+
+ะัะบัะพะนัะต ะฑัะฐัะทะตั ะฟะพ ะฐะดัะตัั
http://127.0.0.1:8000.
+
+ะขะฐะผ ะฒั ะผะพะถะตัะต ะทะฐะดะฐัั:
+
+* "Item ID", ะธัะฟะพะปัะทัะตะผัะน ะฒ ะฟััะธ.
+* "Token", ะธัะฟะพะปัะทัะตะผัะน ะบะฐะบ query-ะฟะฐัะฐะผะตัั.
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะฑัะฐัะธัะต ะฒะฝะธะผะฐะฝะธะต, ััะพ query-ะฟะฐัะฐะผะตัั `token` ะฑัะดะตั ะพะฑัะฐะฑะพัะฐะฝ ะฒ ะทะฐะฒะธัะธะผะพััะธ.
+
+///
+
+ะขะตะฟะตัั ะฒั ะผะพะถะตัะต ะฟะพะดะบะปััะธัััั ะบ ะฒะตะฑ-ัะพะบะตัั ะธ ะฝะฐัะธะฝะฐัั ะพัะฟัะฐะฒะบั ะธ ะฟะพะปััะตะฝะธะต ัะพะพะฑัะตะฝะธะน:
+
+

+
+## ะะฑัะฐะฑะพัะบะฐ ะพัะบะปััะตะฝะธะน ะธ ัะฐะฑะพัะฐ ั ะฝะตัะบะพะปัะบะธะผะธ ะบะปะธะตะฝัะฐะผะธ
+
+ะัะปะธ ะฒะตะฑ-ัะพะบะตั ัะพะตะดะธะฝะตะฝะธะต ะทะฐะบัััะพ, ัะพ `await websocket.receive_text()` ะฒัะทะพะฒะตั ะธัะบะปััะตะฝะธะต `WebSocketDisconnect`, ะบะพัะพัะพะต ะผะพะถะฝะพ ะฟะพะนะผะฐัั ะธ ะพะฑัะฐะฑะพัะฐัั ะบะฐะบ ะฒ ััะพะผ ะฟัะธะผะตัะต:
+
+{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
+
+ะงัะพะฑั ะฒะพัะฟัะพะธะทะฒะตััะธ ะฟัะธะผะตั:
+
+* ะัะบัะพะนัะต ะฟัะธะปะพะถะตะฝะธะต ะฒ ะฝะตัะบะพะปัะบะธั
ะฒะบะปะฐะดะบะฐั
ะฑัะฐัะทะตัะฐ.
+* ะัะฟัะฐะฒััะต ะธะท ะฝะธั
ัะพะพะฑัะตะฝะธั.
+* ะะฐัะตะผ ะทะฐะบัะพะนัะต ะพะดะฝั ะธะท ะฒะบะปะฐะดะพะบ.
+
+ะญัะพ ะฒัะทะพะฒะตั ะธัะบะปััะตะฝะธะต `WebSocketDisconnect`, ะธ ะฒัะต ะพััะฐะปัะฝัะต ะบะปะธะตะฝัั ะฟะพะปััะฐั ัะปะตะดัััะตะต ัะพะพะฑัะตะฝะธะต:
+
+```
+Client #1596980209979 left the chat
+```
+
+/// tip | ะัะธะผะตัะฐะฝะธะต
+
+ะัะธะปะพะถะตะฝะธะต ะฒััะต - ััะพ ะฒัะตะณะพ ะปะธัั ะฟัะพััะพะน ะผะธะฝะธะผะฐะปัะฝัะน ะฟัะธะผะตั, ะดะตะผะพะฝัััะธััััะธะน ะพะฑัะฐะฑะพัะบั ะธ ะฟะตัะตะดะฐัั ัะพะพะฑัะตะฝะธะน ะฝะตัะบะพะปัะบะธะผ ะฒะตะฑ-ัะพะบะตั ัะพะตะดะธะฝะตะฝะธัะผ.
+
+ะะพ ะธะผะตะนัะต ะฒ ะฒะธะดั, ััะพ ััะพ ะฑัะดะตั ัะฐะฑะพัะฐัั ัะพะปัะบะพ ะฒ ะพะดะฝะพะผ ะฟัะพัะตััะต ะธ ัะพะปัะบะพ ะฟะพะบะฐ ะพะฝ ะฐะบัะธะฒะตะฝ, ัะฐะบ ะบะฐะบ ะฒัั ะพะฑัะฐะฑะฐััะฒะฐะตััั ะฒ ะฟัะพััะพะผ ัะฟะธัะบะต ะฒ ะพะฟะตัะฐัะธะฒะฝะพะน ะฟะฐะผััะธ.
+
+ะัะปะธ ะฝัะถะฝะพ ััะพ-ัะพ ะปะตะณะบะพ ะธะฝัะตะณัะธััะตะผะพะต ั FastAPI, ะฝะพ ะฑะพะปะตะต ะฝะฐะดะตะถะฝะพะต ะธ ั ะฟะพะดะดะตัะถะบะพะน Redis, PostgreSQL ะธะปะธ ะดััะณะพะณะพ, ัะพ ะผะพะถะฝะพ ะฒะพัะฟะพะปัะทะพะฒะฐัััั
encode/broadcaster.
+
+///
+
+## ะะพะฟะพะปะฝะธัะตะปัะฝะฐั ะธะฝัะพัะผะฐัะธั
+
+ะะปั ะฑะพะปะตะต ะณะปัะฑะพะบะพะณะพ ะธะทััะตะฝะธั ัะตะผั ะฒะพัะฟะพะปัะทัะนัะตัั ะดะพะบัะผะตะฝัะฐัะธะตะน Starlette:
+
+*
The `WebSocket` class.
+*
Class-based WebSocket handling.
diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md
new file mode 100644
index 000000000..7c3dc288f
--- /dev/null
+++ b/docs/ru/docs/tutorial/bigger-applications.md
@@ -0,0 +1,556 @@
+# ะะพะปััะธะต ะฟัะธะปะพะถะตะฝะธั, ะฒ ะบะพัะพััั
ะผะฝะพะณะพ ัะฐะนะปะพะฒ
+
+ะัะธ ะฟะพัััะพะตะฝะธะธ ะฟัะธะปะพะถะตะฝะธั ะธะปะธ ะฒะตะฑ-API ะฝะฐะผ ัะตะดะบะพ ัะดะฐะตััั ะฟะพะผะตััะธัั ะฒัั ะฒ ะพะดะธะฝ ัะฐะนะป.
+
+**FastAPI** ะฟัะตะดะพััะฐะฒะปัะตั ัะดะพะฑะฝัะน ะธะฝััััะผะตะฝัะฐัะธะน, ะบะพัะพััะน ะฟะพะทะฒะพะปัะตั ะฝะฐะผ ััััะบัััะธัะพะฒะฐัั ะฟัะธะปะพะถะตะฝะธะต, ัะพั
ัะฐะฝัั ะฟัะธ ััะพะผ ะฒัั ะฝะตะพะฑั
ะพะดะธะผัั ะณะธะฑะบะพััั.
+
+/// info | ะัะธะผะตัะฐะฝะธะต
+
+ะัะปะธ ะฒั ัะฐะฝััะต ะธัะฟะพะปัะทะพะฒะฐะปะธ Flask, ัะพ ััะพ ะฐะฝะฐะปะพะณ ัะฐะฑะปะพะฝะพะฒ Flask (Flask's Blueprints).
+
+///
+
+## ะัะธะผะตั ััััะบัััั ะฟัะธะปะพะถะตะฝะธั
+
+ะะฐะฒะฐะนัะต ะฟัะตะดะฟะพะปะพะถะธะผ, ััะพ ะฝะฐัะต ะฟัะธะปะพะถะตะฝะธะต ะธะผะตะตั ัะปะตะดััััั ััััะบัััั:
+
+```
+.
+โโโ app
+โย ย โโโ __init__.py
+โย ย โโโ main.py
+โย ย โโโ dependencies.py
+โย ย โโโ routers
+โย ย โ โโโ __init__.py
+โย ย โ โโโ items.py
+โย ย โ โโโ users.py
+โย ย โโโ internal
+โย ย โโโ __init__.py
+โย ย โโโ admin.py
+```
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะฑัะฐัะธัะต ะฒะฝะธะผะฐะฝะธะต, ััะพ ะฒ ะบะฐะถะดะพะผ ะบะฐัะฐะปะพะณะต ะธ ะฟะพะดะบะฐัะฐะปะพะณะต ะธะผะตะตััั ัะฐะนะป `__init__.py`
+
+ะญัะพ ะบะฐะบ ัะฐะท ัะพ, ััะพ ะฟะพะทะฒะพะปัะตั ะธะผะฟะพััะธัะพะฒะฐัั ะบะพะด ะธะท ะพะดะฝะพะณะพ ัะฐะนะปะฐ ะฒ ะดััะณะพะน.
+
+ะะฐะฟัะธะผะตั, ะฒ ัะฐะนะปะต `app/main.py` ะผะพะถะตั ะฑััั ัะปะตะดัััะฐั ัััะพะบะฐ:
+
+```
+from app.routers import items
+```
+
+///
+
+* ะัั ะฟะพะผะตัะฐะตััั ะฒ ะบะฐัะฐะปะพะณะต `app`. ะ ะฝัะผ ัะฐะบะถะต ะฝะฐั
ะพะดะธััั ะฟัััะพะน ัะฐะนะป `app/__init__.py`. ะขะฐะบะธะผ ะพะฑัะฐะทะพะผ, `app` ัะฒะปัะตััั "Python-ะฟะฐะบะตัะพะผ" (ะบะพะปะปะตะบัะธะตะน ะผะพะดัะปะตะน Python).
+* ะะฝ ัะพะดะตัะถะธั ัะฐะนะป `app/main.py`. ะะฐะฝะฝัะน ัะฐะนะป ัะฒะปัะตััั ัะฐัััั ะฟะฐะบะตัะฐ (ั.ะต. ะฝะฐั
ะพะดะธััั ะฒะฝัััะธ ะบะฐัะฐะปะพะณะฐ, ัะพะดะตัะถะฐัะตะณะพ ัะฐะนะป `__init__.py`), ะธ, ัะพะพัะฒะตัััะฒะตะฝะฝะพ, ะพะฝ ัะฒะปัะตััั ะผะพะดัะปะตะผ ะฟะฐะบะตัะฐ: `app.main`.
+* ะะฝ ัะฐะบะถะต ัะพะดะตัะถะธั ัะฐะนะป `app/dependencies.py`, ะบะพัะพััะน ัะฐะบะถะต, ะบะฐะบ ะธ `app/main.py`, ัะฒะปัะตััั ะผะพะดัะปะตะผ: `app.dependencies`.
+* ะะดะตัั ัะฐะบะถะต ะฝะฐั
ะพะดะธััั ะฟะพะดะบะฐัะฐะปะพะณ `app/routers/`, ัะพะดะตัะถะฐัะธะน `__init__.py`. ะะฝ ัะฒะปัะตััั ััะฑ-ะฟะฐะบะตัะพะผ: `app.routers`.
+* ะคะฐะนะป `app/routers/items.py` ะฝะฐั
ะพะดะธััั ะฒะฝัััะธ ะฟะฐะบะตัะฐ `app/routers/`. ะขะฐะบะธะผ ะพะฑัะฐะทะพะผ, ะพะฝ ัะฒะปัะตััั ััะฑ-ะผะพะดัะปะตะผ: `app.routers.items`.
+* ะขะพัะฝะพ ัะฐะบะถะต `app/routers/users.py` ัะฒะปัะตััั ะตัั ะพะดะฝะธะผ ััะฑ-ะผะพะดัะปะตะผ: `app.routers.users`.
+* ะะพะดะบะฐัะฐะปะพะณ `app/internal/`, ัะพะดะตัะถะฐัะธะน ัะฐะนะป `__init__.py`, ัะฒะปัะตััั ะตัั ะพะดะฝะธะผ ััะฑ-ะฟะฐะบะตัะพะผ: `app.internal`.
+* ะ ัะฐะนะป `app/internal/admin.py` ัะฒะปัะตััั ะตัั ะพะดะฝะธะผ ััะฑ-ะผะพะดัะปะตะผ: `app.internal.admin`.
+
+

+
+ะขะฐ ะถะต ัะฐะผะฐั ัะฐะนะปะพะฒะฐั ััััะบัััะฐ ะฟัะธะปะพะถะตะฝะธั, ะฝะพ ั ะบะพะผะผะตะฝัะฐัะธัะผะธ:
+
+```
+.
+โโโ app # "app" ะฟะฐะบะตั
+โย ย โโโ __init__.py # ััะพั ัะฐะนะป ะฟัะตะฒัะฐัะฐะตั "app" ะฒ "Python-ะฟะฐะบะตั"
+โย ย โโโ main.py # ะผะพะดัะปั "main", ะฝะฐะฟั.: import app.main
+โย ย โโโ dependencies.py # ะผะพะดัะปั "dependencies", ะฝะฐะฟั.: import app.dependencies
+โย ย โโโ routers # ััะฑ-ะฟะฐะบะตั "routers"
+โย ย โ โโโ __init__.py # ะฟัะตะฒัะฐัะฐะตั "routers" ะฒ ััะฑ-ะฟะฐะบะตั
+โย ย โ โโโ items.py # ััะฑ-ะผะพะดัะปั "items", ะฝะฐะฟั.: import app.routers.items
+โย ย โ โโโ users.py # ััะฑ-ะผะพะดัะปั "users", ะฝะฐะฟั.: import app.routers.users
+โย ย โโโ internal # ััะฑ-ะฟะฐะบะตั "internal"
+โย ย โโโ __init__.py # ะฟัะตะฒัะฐัะฐะตั "internal" ะฒ ััะฑ-ะฟะฐะบะตั
+โย ย โโโ admin.py # ััะฑ-ะผะพะดัะปั "admin", ะฝะฐะฟั.: import app.internal.admin
+```
+
+## `APIRouter`
+
+ะะฐะฒะฐะนัะต ะฟัะตะดะฟะพะปะพะถะธะผ, ััะพ ะดะปั ัะฐะฑะพัั ั ะฟะพะปัะทะพะฒะฐัะตะปัะผะธ ะธัะฟะพะปัะทัะตััั ะพัะดะตะปัะฝัะน ัะฐะนะป (ััะฑ-ะผะพะดัะปั) `/app/routers/users.py`.
+
+ะะปั ะปัััะตะน ะพัะณะฐะฝะธะทะฐัะธะธ ะฟัะธะปะพะถะตะฝะธั, ะฒั ั
ะพัะธัะต ะพัะดะตะปะธัั ะพะฟะตัะฐัะธะธ ะฟััะธ, ัะฒัะทะฐะฝะฝัะต ั ะฟะพะปัะทะพะฒะฐัะตะปัะผะธ, ะพั ะพััะฐะปัะฝะพะณะพ ะบะพะดะฐ.
+
+ะะพ ัะฐะบ, ััะพะฑั ััะธ ะพะฟะตัะฐัะธะธ ะฟะพ-ะฟัะตะถะฝะตะผั ะพััะฐะฒะฐะปะธัั ัะฐัััั **FastAPI** ะฟัะธะปะพะถะตะฝะธั/ะฒะตะฑ-API (ัะฐัััั ะพะดะฝะพะณะพ ะฟะฐะบะตัะฐ)
+
+ะก ะฟะพะผะพััั `APIRouter` ะฒั ะผะพะถะตัะต ัะพะทะดะฐัั *ะพะฟะตัะฐัะธะธ ะฟััะธ* (*ัะฝะดะฟะพะธะฝัั*) ะดะปั ะดะฐะฝะฝะพะณะพ ะผะพะดัะปั.
+
+
+### ะะผะฟะพัั `APIRouter`
+
+ะขะพัะฝะพ ัะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะปััะฐะต ั ะบะปะฐััะพะผ `FastAPI`, ะฒะฐะผ ะฝัะถะฝะพ ะธะผะฟะพััะธัะพะฒะฐัั ะธ ัะพะทะดะฐัั ะพะฑัะตะบั ะบะปะฐััะฐ `APIRouter`.
+
+```Python hl_lines="1 3" title="app/routers/users.py"
+{!../../docs_src/bigger_applications/app/routers/users.py!}
+```
+
+### ะกะพะทะดะฐะฝะธะต *ัะฝะดะฟะพะธะฝัะพะฒ* ั ะฟะพะผะพััั `APIRouter`
+
+ะ ะดะฐะปัะฝะตะนัะตะผ ะธัะฟะพะปัะทัะนัะต `APIRouter` ะดะปั ะพะฑััะฒะปะตะฝะธั *ัะฝะดะฟะพะธะฝัะพะฒ*, ัะพัะฝะพ ัะฐะบะถะต, ะบะฐะบ ะฒั ะธัะฟะพะปัะทัะตัะต ะบะปะฐัั `FastAPI`:
+
+```Python hl_lines="6 11 16" title="app/routers/users.py"
+{!../../docs_src/bigger_applications/app/routers/users.py!}
+```
+
+ะั ะผะพะถะตัะต ะดัะผะฐัั ะพะฑ `APIRouter` ะบะฐะบ ะพะฑ "ัะผะตะฝััะตะฝะฝะพะน ะฒะตััะธะธ" ะบะปะฐััะฐ FastAPI`.
+
+`APIRouter` ะฟะพะดะดะตัะถะธะฒะฐะตั ะฒัะต ัะต ะถะต ัะฐะผัะต ะพะฟัะธะธ.
+
+`APIRouter` ะฟะพะดะดะตัะถะธะฒะฐะตั ะฒัะต ัะต ะถะต ัะฐะผัะต ะฟะฐัะฐะผะตััั, ัะฐะบะธะต ะบะฐะบ `parameters`, `responses`, `dependencies`, `tags`, ะธ ั. ะด.
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะ ะดะฐะฝะฝะพะผ ะฟัะธะผะตัะต, ะฒ ะบะฐัะตััะฒะต ะฝะฐะทะฒะฐะฝะธั ะฟะตัะตะผะตะฝะฝะพะน ะธัะฟะพะปัะทัะตััั `router`, ะฝะพ ะฒั ะผะพะถะตัะต ะธัะฟะพะปัะทะพะฒะฐัั ะปัะฑะพะต ะดััะณะพะต ะธะผั.
+
+///
+
+ะั ัะพะฑะธัะฐะตะผัั ะฟะพะดะบะปััะธัั ะดะฐะฝะฝัะน `APIRouter` ะบ ะฝะฐัะตะผั ะพัะฝะพะฒะฝะพะผั ะฟัะธะปะพะถะตะฝะธั ะฝะฐ `FastAPI`, ะฝะพ ัะฝะฐัะฐะปะฐ ะดะฐะฒะฐะนัะต ะฟัะพะฒะตัะธะผ ะทะฐะฒะธัะธะผะพััะธ ะธ ัะพะทะดะฐะดะธะผ ะตัั ะพะดะธะฝ ะผะพะดัะปั ั `APIRouter`.
+
+## ะะฐะฒะธัะธะผะพััะธ
+
+ะะฐะผ ะฟะพะฝะฐะดะพะฑัััั ะฝะตะบะพัะพััะต ะทะฐะฒะธัะธะผะพััะธ, ะบะพัะพััะต ะผั ะฑัะดะตะผ ะธัะฟะพะปัะทะพะฒะฐัั ะฒ ัะฐะทะฝัั
ะผะตััะฐั
ะฝะฐัะตะณะพ ะฟัะธะปะพะถะตะฝะธั.
+
+ะั ะฟะพะผะตััะธะผ ะธั
ะฒ ะพัะดะตะปัะฝัะน ะผะพะดัะปั `dependencies` (`app/dependencies.py`).
+
+ะขะตะฟะตัั ะผั ะฒะพัะฟะพะปัะทัะตะผัั ะฟัะพััะพะน ะทะฐะฒะธัะธะผะพัััั, ััะพะฑั ะฟัะพัะธัะฐัั ะบะฐััะพะผะธะทะธัะพะฒะฐะฝะฝัะน `X-Token` ะธะท ะทะฐะณะพะปะพะฒะบะฐ:
+
+//// tab | Python 3.9+
+
+```Python hl_lines="3 6-8" title="app/dependencies.py"
+{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python hl_lines="1 5-7" title="app/dependencies.py"
+{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะั ัะตะบะพะผะตะฝะดัะตะผ ะธัะฟะพะปัะทะพะฒะฐัั ะฒะตััะธั `Annotated`, ะบะพะณะดะฐ ััะพ ะฒะพะทะผะพะถะฝะพ.
+
+///
+
+```Python hl_lines="1 4-6" title="app/dependencies.py"
+{!> ../../docs_src/bigger_applications/app/dependencies.py!}
+```
+
+////
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะปั ะฟัะพััะพัั ะผั ะฒะพัะฟะพะปัะทะพะฒะฐะปะธัั ะฝะตะบะธะผ ะฒะพะพะฑัะฐะถะฐะตะผัะผ ะทะฐะณะพะปะพะฒะพะบะพะผ.
+
+ะ ัะตะฐะปัะฝัั
ัะปััะฐัั
ะดะปั ะฟะพะปััะตะฝะธั ะฝะฐะธะปัััะธั
ัะตะทัะปััะฐัะพะฒ ะธัะฟะพะปัะทัะนัะต ะธะฝัะตะณัะธัะพะฒะฐะฝะฝัะต ััะธะปะธัั ะพะฑะตัะฟะตัะตะฝะธั ะฑะตะทะพะฟะฐัะฝะพััะธ [Security utilities](security/index.md){.internal-link target=_blank}.
+
+///
+
+## ะัั ะพะดะธะฝ ะผะพะดัะปั ั `APIRouter`
+
+ะะฐะฒะฐะนัะต ัะฐะบะถะต ะฟัะตะดะฟะพะปะพะถะธะผ, ััะพ ั ะฒะฐั ะตััั *ัะฝะดะฟะพะธะฝัั*, ะพัะฒะตัะฐััะธะต ะทะฐ ะพะฑัะฐะฑะพัะบั "items", ะธ ะพะฝะธ ะฝะฐั
ะพะดัััั ะฒ ะผะพะดัะปะต `app/routers/items.py`.
+
+ะฃ ะฒะฐั ะพะฟัะตะดะตะปะตะฝั ัะปะตะดัััะธะต *ะพะฟะตัะฐัะธะธ ะฟััะธ* (*ัะฝะดะฟะพะธะฝัั*):
+
+* `/items/`
+* `/items/{item_id}`
+
+ะขัั ะฒัั ัะพัะฝะพ ัะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะธััะฐัะธะธ ั `app/routers/users.py`.
+
+ะะพ ัะตะฟะตัั ะผั ั
ะพัะธะผ ะฟะพัััะฟะธัั ะฝะตะผะฝะพะณะพ ัะผะฝะตะต ะธ ัะปะตะณะบะฐ ัะฟัะพััะธัั ะบะพะด.
+
+ะั ะทะฝะฐะตะผ, ััะพ ะฒัะต *ัะฝะดะฟะพะธะฝัั* ะดะฐะฝะฝะพะณะพ ะผะพะดัะปั ะธะผะตัั ะฝะตะบะพัะพััะต ะพะฑัะธะต ัะฒะพะนััะฒะฐ:
+
+* ะัะตัะธะบั ะฟััะธ: `/items`.
+* ะขะตะณะธ: (ะพะดะธะฝ ะตะดะธะฝััะฒะตะฝะฝัะน ัะตะณ: `items`).
+* ะะพะฟะพะปะฝะธัะตะปัะฝัะต ะพัะฒะตัั (responses)
+* ะะฐะฒะธัะธะผะพััะธ: ะธัะฟะพะปัะทะพะฒะฐะฝะธะต ัะพะทะดะฐะฝะฝะพะน ะฝะฐะผะธ ะทะฐะฒะธัะธะผะพััะธ `X-token`
+
+ะขะฐะบะธะผ ะพะฑัะฐะทะพะผ, ะฒะผะตััะพ ัะพะณะพ ััะพะฑั ะดะพะฑะฐะฒะปััั ะฒัะต ััะธ ัะฒะพะนััะฒะฐ ะฒ ััะฝะบัะธั ะบะฐะถะดะพะณะพ ะพัะดะตะปัะฝะพะณะพ *ัะฝะดะฟะพะธะฝัะฐ*,
+ะผั ะดะพะฑะฐะฒะธะผ ะธั
ะฒ `APIRouter`.
+
+```Python hl_lines="5-10 16 21" title="app/routers/items.py"
+{!../../docs_src/bigger_applications/app/routers/items.py!}
+```
+
+ะขะฐะบ ะบะฐะบ ะบะฐะถะดัะน *ัะฝะดะฟะพะธะฝั* ะฝะฐัะธะฝะฐะตััั ั ัะธะผะฒะพะปะฐ `/`:
+
+```Python hl_lines="1"
+@router.get("/{item_id}")
+async def read_item(item_id: str):
+ ...
+```
+
+...ัะพ ะฟัะตัะธะบั ะฝะต ะดะพะปะถะตะฝ ะทะฐะบะฐะฝัะธะฒะฐัััั ัะธะผะฒะพะปะพะผ `/`.
+
+ะ ะฝะฐัะตะผ ัะปััะฐะต ะฟัะตัะธะบัะพะผ ัะฒะปัะตััั `/items`.
+
+ะั ัะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะธัั ะฒ ะฝะฐั ะผะฐัััััะธะทะฐัะพั (router) ัะฟะธัะพะบ `ัะตะณะพะฒ` (`tags`) ะธ ะดะพะฟะพะปะฝะธัะตะปัะฝัั
`ะพัะฒะตัะพะฒ` (`responses`), ะบะพัะพััะต ัะฒะปััััั ะพะฑัะธะผะธ ะดะปั ะบะฐะถะดะพะณะพ *ัะฝะดะฟะพะธะฝัะฐ*.
+
+ะ ะตัั ะผั ะผะพะถะตะผ ะดะพะฑะฐะฒะธัั ะฒ ะฝะฐั ะผะฐัััััะธะทะฐัะพั ัะฟะธัะพะบ `ะทะฐะฒะธัะธะผะพััะตะน`, ะบะพัะพััะต ะดะพะปะถะฝั ะฒัะทัะฒะฐัััั ะฟัะธ ะบะฐะถะดะพะผ ะพะฑัะฐัะตะฝะธะธ ะบ *ัะฝะดะฟะพะธะฝัะฐะผ*.
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะฑัะฐัะธัะต ะฒะฝะธะผะฐะฝะธะต, ััะพ ัะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะปััะฐะต ั ะทะฐะฒะธัะธะผะพัััะผะธ ะฒ ะดะตะบะพัะฐัะพัะฐั
*ัะฝะดะฟะพะธะฝัะพะฒ* ([dependencies in *path operation decorators*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ะฝะธะบะฐะบะพะณะพ ะทะฝะฐัะตะฝะธั ะฒ *ััะฝะบัะธั ัะฝะดะฟะพะธะฝัะฐ* ะฟะตัะตะดะฐะฝะพ ะฝะต ะฑัะดะตั.
+
+///
+
+ะ ัะตะทัะปััะฐัะต ะผั ะฟะพะปััะธะผ ัะปะตะดัััะธะต ัะฝะดะฟะพะธะฝัั:
+
+* `/items/`
+* `/items/{item_id}`
+
+...ะบะฐะบ ะผั ะธ ะฟะปะฐะฝะธัะพะฒะฐะปะธ.
+
+* ะะฝะธ ะฑัะดัั ะฟะพะผะตัะตะฝั ัะตะณะฐะผะธ ะธะท ะทะฐะดะฐะฝะฝะพะณะพ ัะฟะธัะบะฐ, ะฒ ะฝะฐัะตะผ ัะปััะฐะต ััะพ `"items"`.
+ * ะญัะธ ัะตะณะธ ะพัะพะฑะตะฝะฝะพ ะฟะพะปะตะทะฝั ะดะปั ัะธััะตะผั ะฐะฒัะพะผะฐัะธัะตัะบะพะน ะธะฝัะตัะฐะบัะธะฒะฝะพะน ะดะพะบัะผะตะฝัะฐัะธะธ (ั ะธัะฟะพะปัะทะพะฒะฐะฝะธะตะผ OpenAPI).
+* ะะฐะถะดัะน ะธะท ะฝะธั
ะฑัะดะตั ะฒะบะปััะฐัั ะฟัะตะดะพะฟัะตะดะตะปะตะฝะฝัะต ะพัะฒะตัั `responses`.
+* ะะฐะถะดัะน *ัะฝะดะฟะพะธะฝั* ะฑัะดะตั ะธะผะตัั ัะฟะธัะพะบ ะทะฐะฒะธัะธะผะพััะตะน (`dependencies`), ะธัะฟะพะปะฝัะตะผัั
ะฟะตัะตะด ะฒัะทะพะฒะพะผ *ัะฝะดะฟะพะธะฝัะฐ*.
+ * ะัะปะธ ะฒั ะพะฟัะตะดะตะปะธะปะธ ะทะฐะฒะธัะธะผะพััะธ ะฒ ัะฐะผะพะน ะพะฟะตัะฐัะธะธ ะฟััะธ, **ัะพ ะพะฝะฐ ัะฐะบะถะต ะฑัะดะตั ะฒัะฟะพะปะฝะตะฝะฐ**.
+ * ะกะฝะฐัะฐะปะฐ ะฒัะฟะพะปะฝััััั ะทะฐะฒะธัะธะผะพััะธ ะผะฐัััััะธะทะฐัะพัะฐ, ะทะฐัะตะผ ะฒัะทัะฒะฐัััั ะทะฐะฒะธัะธะผะพััะธ, ะพะฟัะตะดะตะปะตะฝะฝัะต ะฒ ะดะตะบะพัะฐัะพัะต *ัะฝะดะฟะพะธะฝัะฐ* ([`dependencies` in the decorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ะธ, ะฝะฐะบะพะฝะตั, ะพะฑััะฝัะต ะฟะฐัะฐะผะตััะธัะตัะบะธะต ะทะฐะฒะธัะธะผะพััะธ.
+ * ะั ัะฐะบะถะต ะผะพะถะตัะต ะดะพะฑะฐะฒะธัั ะทะฐะฒะธัะธะผะพััะธ ะฑะตะทะพะฟะฐัะฝะพััะธ ั ะพะฑะปะฐัััะผะธ ะฒะธะดะธะผะพััะธ (`scopes`) [`Security` dependencies with `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะฐะฟัะธะผะตั, ั ะฟะพะผะพััั ะทะฐะฒะธัะธะผะพััะตะน ะฒ `APIRouter` ะผั ะผะพะถะตะผ ะฟะพััะตะฑะพะฒะฐัั ะฐััะตะฝัะธัะธะบะฐัะธะธ ะดะปั ะดะพัััะฟะฐ ะบะพ ะฒัะตะน ะณััะฟะฟะต *ัะฝะดะฟะพะธะฝัะพะฒ*, ะฝะต ัะบะฐะทัะฒะฐั ะทะฐะฒะธัะธะผะพััะธ ะดะปั ะบะฐะถะดะพะน ะพัะดะตะปัะฝะพะน ััะฝะบัะธะธ *ัะฝะดะฟะพะธะฝัะฐ*.
+
+///
+
+/// check | ะะฐะผะตัะบะฐ
+
+ะะฐัะฐะผะตััั `prefix`, `tags`, `responses` ะธ `dependencies` ะพัะฝะพััััั ะบ ััะฝะบัะธะพะฝะฐะปั **FastAPI**, ะฟะพะผะพะณะฐััะตะผั ะธะทะฑะตะถะฐัั ะดัะฑะปะธัะพะฒะฐะฝะธั ะบะพะดะฐ.
+
+///
+
+### ะะผะฟะพัั ะทะฐะฒะธัะธะผะพััะตะน
+
+ะะฐั ะบะพะด ะฝะฐั
ะพะดะธััั ะฒ ะผะพะดัะปะต `app.routers.items` (ัะฐะนะป `app/routers/items.py`).
+
+ะ ะฝะฐะผ ะฝัะถะฝะพ ะฒัะทะฒะฐัั ััะฝะบัะธั ะทะฐะฒะธัะธะผะพััะธ ะธะท ะผะพะดัะปั `app.dependencies` (ัะฐะนะป `app/dependencies.py`).
+
+ะั ะธัะฟะพะปัะทัะตะผ ะพะฟะตัะฐัะธั ะพัะฝะพัะธัะตะปัะฝะพะณะพ ะธะผะฟะพััะฐ `..` ะดะปั ะธะผะฟะพััะฐ ะทะฐะฒะธัะธะผะพััะธ:
+
+```Python hl_lines="3" title="app/routers/items.py"
+{!../../docs_src/bigger_applications/app/routers/items.py!}
+```
+
+#### ะะฐะบ ัะฐะฑะพัะฐะตั ะพัะฝะพัะธัะตะปัะฝัะน ะธะผะฟะพัั?
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะัะปะธ ะฒั ะฟัะตะบัะฐัะฝะพ ะทะฝะฐะตัะต, ะบะฐะบ ัะฐะฑะพัะฐะตั ะธะผะฟะพัั ะฒ Python, ัะพ ะฟะตัะตั
ะพะดะธัะต ะบ ัะปะตะดัััะตะผั ัะฐะทะดะตะปั.
+
+///
+
+ะะดะฝะฐ ัะพัะบะฐ `.`, ะบะฐะบ ะฒ ะดะฐะฝะฝะพะผ ะฟัะธะผะตัะต:
+
+```Python
+from .dependencies import get_token_header
+```
+ะพะทะฝะฐัะฐะตั:
+
+* ะะฐัะฝะธัะต ั ะฟะฐะบะตัะฐ, ะฒ ะบะพัะพัะพะผ ะฝะฐั
ะพะดะธััั ะดะฐะฝะฝัะน ะผะพะดัะปั (ัะฐะนะป `app/routers/items.py` ัะฐัะฟะพะปะพะถะตะฝ ะฒ ะบะฐัะฐะปะพะณะต `app/routers/`)...
+* ... ะฝะฐะนะดะธัะต ะผะพะดัะปั `dependencies` (ัะฐะนะป `app/routers/dependencies.py`)...
+* ... ะธ ะธะผะฟะพััะธััะนัะต ะธะท ะฝะตะณะพ ััะฝะบัะธั `get_token_header`.
+
+ะ ัะพะถะฐะปะตะฝะธั, ัะฐะบะพะณะพ ัะฐะนะปะฐ ะฝะต ัััะตััะฒัะตั, ะธ ะฝะฐัะธ ะทะฐะฒะธัะธะผะพััะธ ะฝะฐั
ะพะดัััั ะฒ ัะฐะนะปะต `app/dependencies.py`.
+
+ะัะฟะพะผะฝะธัะต, ะบะฐะบ ะฒัะณะปัะดะธั ัะฐะนะปะพะฒะฐั ััััะบัััะฐ ะฝะฐัะตะณะพ ะฟัะธะปะพะถะตะฝะธั:
+
+

+
+---
+
+ะะฒะต ัะพัะบะธ `..`, ะบะฐะบ ะฒ ะดะฐะฝะฝะพะผ ะฟัะธะผะตัะต:
+
+```Python
+from ..dependencies import get_token_header
+```
+
+ะพะทะฝะฐัะฐัั:
+
+* ะะฐัะฝะธัะต ั ะฟะฐะบะตัะฐ, ะฒ ะบะพัะพัะพะผ ะฝะฐั
ะพะดะธััั ะดะฐะฝะฝัะน ะผะพะดัะปั (ัะฐะนะป `app/routers/items.py` ะฝะฐั
ะพะดะธััั ะฒ ะบะฐัะฐะปะพะณะต `app/routers/`)...
+* ... ะฟะตัะตะนะดะธัะต ะฒ ัะพะดะธัะตะปััะบะธะน ะฟะฐะบะตั (ะบะฐัะฐะปะพะณ `app/`)...
+* ... ะฝะฐะนะดะธัะต ะฒ ะฝัะผ ะผะพะดัะปั `dependencies` (ัะฐะนะป `app/dependencies.py`)...
+* ... ะธ ะธะผะฟะพััะธััะนัะต ะธะท ะฝะตะณะพ ััะฝะบัะธั `get_token_header`.
+
+ะญัะพ ัะฐะฑะพัะฐะตั ะฒะตัะฝะพ! ๐
+
+---
+
+ะะฝะฐะปะพะณะธัะฝะพ, ะตัะปะธ ะฑั ะผั ะธัะฟะพะปัะทะพะฒะฐะปะธ ััะธ ัะพัะบะธ `...`, ะบะฐะบ ะทะดะตัั:
+
+```Python
+from ...dependencies import get_token_header
+```
+
+ัะพ ััะพ ะฑั ะพะทะฝะฐัะฐะปะพ:
+
+* ะะฐัะฝะธัะต ั ะฟะฐะบะตัะฐ, ะฒ ะบะพัะพัะพะผ ะฝะฐั
ะพะดะธััั ะดะฐะฝะฝัะน ะผะพะดัะปั (ัะฐะนะป `app/routers/items.py` ะฝะฐั
ะพะดะธััั ะฒ ะบะฐัะฐะปะพะณะต `app/routers/`)...
+* ... ะฟะตัะตะนะดะธัะต ะฒ ัะพะดะธัะตะปััะบะธะน ะฟะฐะบะตั (ะบะฐัะฐะปะพะณ `app/`)...
+* ... ะทะฐัะตะผ ะฟะตัะตะนะดะธัะต ะฒ ัะพะดะธัะตะปััะบะธะน ะฟะฐะบะตั ัะตะบััะตะณะพ ะฟะฐะบะตัะฐ (ัะฐะบะพะณะพ ะฟะฐะบะตัะฐ ะฝะต ัััะตััะฒัะตั, `app` ะฝะฐั
ะพะดะธััั ะฝะฐ ัะฐะผะพะผ ะฒะตัั
ะฝะตะผ ััะพะฒะฝะต ๐ฑ)...
+* ... ะฝะฐะนะดะธัะต ะฒ ะฝัะผ ะผะพะดัะปั `dependencies` (ัะฐะนะป `app/dependencies.py`)...
+* ... ะธ ะธะผะฟะพััะธััะนัะต ะธะท ะฝะตะณะพ ััะฝะบัะธั `get_token_header`.
+
+ะญัะพ ะฑัะดะตั ะพัะฝะพัะธัััั ะบ ะฝะตะบะพัะพัะพะผั ะฟะฐะบะตัั, ะฝะฐั
ะพะดััะตะผััั ะฝะฐ ะพะดะธะฝ ััะพะฒะตะฝั ะฒััะต ัะตะผ `app/` ะธ ัะพะดะตัะถะฐัะตะผั ัะฒะพะน ัะพะฑััะฒะตะฝะฝัะน ัะฐะนะป `__init__.py`. ะะพ ะฝะธัะตะณะพ ัะฐะบะพะณะพ ั ะฝะฐั ะฝะตั. ะะพััะพะผั ััะพ ะฟัะธะฒะตะดะตั ะบ ะพัะธะฑะบะต ะฒ ะฝะฐัะตะผ ะฟัะธะผะตัะต. ๐จ
+
+ะขะตะฟะตัั ะฒั ะทะฝะฐะตัะต, ะบะฐะบ ัะฐะฑะพัะฐะตั ะธะผะฟะพัั ะฒ Python, ะธ ัะผะพะถะตัะต ะธัะฟะพะปัะทะพะฒะฐัั ะพัะฝะพัะธัะตะปัะฝะพะต ะธะผะฟะพััะธัะพะฒะฐะฝะธะต ะฒ ัะฒะพะธั
ัะพะฑััะฒะตะฝะฝัั
ะฟัะธะปะพะถะตะฝะธัั
ะปัะฑะพะณะพ ััะพะฒะฝั ัะปะพะถะฝะพััะธ. ๐ค
+
+### ะะพะฑะฐะฒะปะตะฝะธะต ะฟะพะปัะทะพะฒะฐัะตะปััะบะธั
ัะตะณะพะฒ (`tags`), ะพัะฒะตัะพะฒ (`responses`) ะธ ะทะฐะฒะธัะธะผะพััะตะน (`dependencies`)
+
+ะั ะฝะต ะฑัะดะตะผ ะดะพะฑะฐะฒะปััั ะฟัะตัะธะบั `/items` ะธ ัะฟะธัะพะบ ัะตะณะพะฒ `tags=["items"]` ะดะปั ะบะฐะถะดะพะณะพ *ัะฝะดะฟะพะธะฝัะฐ*, ั.ะบ. ะผั ัะถะต ะธั
ะดะพะฑะฐะฒะธะปะธ ั ะฟะพะผะพััั `APIRouter`.
+
+ะะพ ะฟะพะผะธะผะพ ััะพะณะพ ะผั ะผะพะถะตะผ ะดะพะฑะฐะฒะธัั ะฝะพะฒัะต ัะตะณะธ ะดะปั ะบะฐะถะดะพะณะพ ะพัะดะตะปัะฝะพะณะพ *ัะฝะดะฟะพะธะฝัะฐ*, ะฐ ัะฐะบะถะต ะฝะตะบะพัะพััะต ะดะพะฟะพะปะฝะธัะตะปัะฝัะต ะพัะฒะตัั (`responses`), ั
ะฐัะฐะบัะตัะฝัะต ะดะปั ะดะฐะฝะฝะพะณะพ *ัะฝะดะฟะพะธะฝัะฐ*:
+
+```Python hl_lines="30-31" title="app/routers/items.py"
+{!../../docs_src/bigger_applications/app/routers/items.py!}
+```
+
+/// tip | ะะพะดัะบะฐะทะบะฐ
+
+ะะพัะปะตะดะฝะธะน *ัะฝะดะฟะพะธะฝั* ะฑัะดะตั ะธะผะตัั ัะปะตะดััััั ะบะพะผะฑะธะฝะฐัะธั ัะตะณะพะฒ: `["items", "custom"]`.
+
+ะ ัะฐะบะถะต ะฒ ะตะณะพ ะดะพะบัะผะตะฝัะฐัะธะธ ะฑัะดัั ัะพะดะตัะถะฐัััั ะพะฑะฐ ะพัะฒะตัะฐ: ะพะดะธะฝ ะดะปั `404` ะธ ะดััะณะพะน ะดะปั `403`.
+
+///
+
+## ะะพะดัะปั main ะฒ `FastAPI`
+
+ะขะตะฟะตัั ะดะฐะฒะฐะนัะต ะฟะพัะผะพััะธะผ ะฝะฐ ะผะพะดัะปั `app/main.py`.
+
+ะะผะตะฝะฝะพ ััะดะฐ ะฒั ะธะผะฟะพััะธััะตัะต ะธ ะธะผะตะฝะฝะพ ะทะดะตัั ะฒั ะธัะฟะพะปัะทัะตัะต ะบะปะฐัั `FastAPI`.
+
+ะญัะพ ะพัะฝะพะฒะฝะพะน ัะฐะนะป ะฒะฐัะตะณะพ ะฟัะธะปะพะถะตะฝะธั, ะบะพัะพััะน ะพะฑัะตะดะธะฝัะตั ะฒัั ะฒ ะพะดะฝะพ ัะตะปะพะต.
+
+ะ ัะตะฟะตัั, ะบะพะณะดะฐ ะฑะพะปััะฐั ัะฐััั ะปะพะณะธะบะธ ะฟัะธะปะพะถะตะฝะธั ัะฐะทะดะตะปะตะฝะฐ ะฝะฐ ะพัะดะตะปัะฝัะต ะผะพะดัะปะธ, ะพัะฝะพะฒะฝะพะน ัะฐะนะป `app/main.py` ะฑัะดะตั ะดะพััะฐัะพัะฝะพ ะฟัะพัััะผ.
+
+### ะะผะฟะพัั `FastAPI`
+
+ะั ะธะผะฟะพััะธััะตัะต ะธ ัะพะทะดะฐะตัะต ะบะปะฐัั `FastAPI` ะบะฐะบ ะพะฑััะฝะพ.
+
+ะั ะดะฐะถะต ะผะพะถะตะผ ะพะฑััะฒะธัั ะณะปะพะฑะฐะปัะฝัะต ะทะฐะฒะธัะธะผะพััะธ [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}, ะบะพัะพััะต ะฑัะดัั ะพะฑัะตะดะธะฝะตะฝั ั ะทะฐะฒะธัะธะผะพัััะผะธ ะดะปั ะบะฐะถะดะพะณะพ ะพัะดะตะปัะฝะพะณะพ ะผะฐัััััะธะทะฐัะพัะฐ:
+
+```Python hl_lines="1 3 7" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+### ะะผะฟะพัั `APIRouter`
+
+ะขะตะฟะตัั ะผั ะธะผะฟะพััะธััะตะผ ะดััะณะธะต ััะฑ-ะผะพะดัะปะธ, ัะพะดะตัะถะฐัะธะต `APIRouter`:
+
+```Python hl_lines="4-5" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+ะขะฐะบ ะบะฐะบ ัะฐะนะปั `app/routers/users.py` ะธ `app/routers/items.py` ัะฒะปััััั ััะฑ-ะผะพะดัะปัะผะธ ะพะดะฝะพะณะพ ะธ ัะพะณะพ ะถะต Python-ะฟะฐะบะตัะฐ `app`, ัะพ ะผั ัะผะพะถะตะผ ะธั
ะธะผะฟะพััะธัะพะฒะฐัั, ะฒะพัะฟะพะปัะทะพะฒะฐะฒัะธัั ะพะฟะตัะฐัะธะตะน ะพัะฝะพัะธัะตะปัะฝะพะณะพ ะธะผะฟะพััะฐ `.`.
+
+### ะะฐะบ ัะฐะฑะพัะฐะตั ะธะผะฟะพัั?
+
+ะะฐะฝะฝะฐั ัััะพะบะฐ ะบะพะดะฐ:
+
+```Python
+from .routers import items, users
+```
+
+ะพะทะฝะฐัะฐะตั:
+
+* ะะฐัะฝะธัะต ั ะฟะฐะบะตัะฐ, ะฒ ะบะพัะพัะพะผ ัะพะดะตัะถะธััั ะดะฐะฝะฝัะน ะผะพะดัะปั (ัะฐะนะป `app/main.py` ัะพะดะตัะถะธััั ะฒ ะบะฐัะฐะปะพะณะต `app/`)...
+* ... ะฝะฐะนะดะธัะต ััะฑ-ะฟะฐะบะตั `routers` (ะบะฐัะฐะปะพะณ `app/routers/`)...
+* ... ะธ ะธะท ะฝะตะณะพ ะธะผะฟะพััะธััะนัะต ััะฑ-ะผะพะดัะปะธ `items` (ัะฐะนะป `app/routers/items.py`) ะธ `users` (ัะฐะนะป `app/routers/users.py`)...
+
+ะ ะผะพะดัะปะต `items` ัะพะดะตัะถะธััั ะฟะตัะตะผะตะฝะฝะฐั `router` (`items.router`), ัะฐ ัะฐะผะฐั, ะบะพัะพััั ะผั ัะพะทะดะฐะปะธ ะฒ ัะฐะนะปะต `app/routers/items.py`, ะพะฝะฐ ัะฒะปัะตััั ะพะฑัะตะบัะพะผ ะบะปะฐััะฐ `APIRouter`.
+
+ะ ะทะฐัะตะผ ะผั ัะดะตะปะฐะตะผ ัะพ ะถะต ัะฐะผะพะต ะดะปั ะผะพะดัะปั `users`.
+
+ะั ัะฐะบะถะต ะผะพะณะปะธ ะฑั ะธะผะฟะพััะธัะพะฒะฐัั ะธ ะดััะณะธะผ ะผะตัะพะดะพะผ:
+
+```Python
+from app.routers import items, users
+```
+
+/// info | ะัะธะผะตัะฐะฝะธะต
+
+ะะตัะฒะฐั ะฒะตััะธั ัะฒะปัะตััั ะฟัะธะผะตัะพะผ ะพัะฝะพัะธัะตะปัะฝะพะณะพ ะธะผะฟะพััะฐ:
+
+```Python
+from .routers import items, users
+```
+
+ะัะพัะฐั ะฒะตััะธั ัะฒะปัะตััั ะฟัะธะผะตัะพะผ ะฐะฑัะพะปััะฝะพะณะพ ะธะผะฟะพััะฐ:
+
+```Python
+from app.routers import items, users
+```
+
+ะฃะทะฝะฐัั ะฑะพะปััะต ะพ ะฟะฐะบะตัะฐั
ะธ ะผะพะดัะปัั
ะฒ Python ะฒั ะผะพะถะตัะต ะธะท
ะพัะธัะธะฐะปัะฝะพะน ะดะพะบัะผะตะฝัะฐัะธะธ Python ะพ ะผะพะดัะปัั
+
+///
+
+### ะะทะฑะตะณะฐะนัะต ะบะพะฝัะปะธะบัะพะฒ ะธะผะตะฝ
+
+ะะผะตััะพ ัะพะณะพ ััะพะฑั ะธะผะฟะพััะธัะพะฒะฐัั ัะพะปัะบะพ ะฟะตัะตะผะตะฝะฝัั `router`, ะผั ะธะผะฟะพััะธััะตะผ ะฝะตะฟะพััะตะดััะฒะตะฝะฝะพ ััะฑ-ะผะพะดัะปั `items`.
+
+ะั ะดะตะปะฐะตะผ ััะพ ะฟะพัะพะผั, ััะพ ั ะฝะฐั ะตััั ะตัั ะพะดะฝะฐ ะฟะตัะตะผะตะฝะฝะฐั `router` ะฒ ััะฑ-ะผะพะดัะปะต `users`.
+
+ะัะปะธ ะฑั ะผั ะธะผะฟะพััะธัะพะฒะฐะปะธ ะธั
ะพะดะฝั ะทะฐ ะดััะณะพะน, ะบะฐะบ ะฟะพะบะฐะทะฐะฝะพ ะฒ ะฟัะธะผะตัะต:
+
+```Python
+from .routers.items import router
+from .routers.users import router
+```
+
+ัะพ ะฟะตัะตะผะตะฝะฝะฐั `router` ะธะท `users` ะฟะตัะตะฟะธัะฐะป ะฑั ะฟะตัะตะผะตะฝะฝัั `router` ะธะท `items`, ะธ ั ะฝะฐั ะฝะต ะฑัะปะพ ะฑั ะฒะพะทะผะพะถะฝะพััะธ ะธัะฟะพะปัะทะพะฒะฐัั ะธั
ะพะดะฝะพะฒัะตะผะตะฝะฝะพ.
+
+ะะพััะพะผั, ะดะปั ัะพะณะพ ััะพะฑั ะธัะฟะพะปัะทะพะฒะฐัั ะพะฑะต ััะธ ะฟะตัะตะผะตะฝะฝัะต ะฒ ะพะดะฝะพะผ ัะฐะนะปะต, ะผั ะธะผะฟะพััะธัะพะฒะฐะปะธ ัะพะพัะฒะตัััะฒัััะธะต ััะฑ-ะผะพะดัะปะธ:
+
+```Python hl_lines="5" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+### ะะพะดะบะปััะตะฝะธะต ะผะฐัััััะธะทะฐัะพัะพะฒ (`APIRouter`) ะดะปั `users` ะธ ะดะปั `items`
+
+ะะฐะฒะฐะนัะต ะฟะพะดะบะปััะธะผ ะผะฐัััััะธะทะฐัะพัั (`router`) ะธะท ััะฑ-ะผะพะดัะปะตะน `users` ะธ `items`:
+
+```Python hl_lines="10-11" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+/// info | ะัะธะผะตัะฐะฝะธะต
+
+`users.router` ัะพะดะตัะถะธั `APIRouter` ะธะท ัะฐะนะปะฐ `app/routers/users.py`.
+
+ะ `items.router` ัะพะดะตัะถะธั `APIRouter` ะธะท ัะฐะนะปะฐ `app/routers/items.py`.
+
+///
+
+ะก ะฟะพะผะพััั `app.include_router()` ะผั ะผะพะถะตะผ ะดะพะฑะฐะฒะธัั ะบะฐะถะดัะน ะธะท ะผะฐัััััะธะทะฐัะพัะพะฒ (`APIRouter`) ะฒ ะพัะฝะพะฒะฝะพะต ะฟัะธะปะพะถะตะฝะธะต `FastAPI`.
+
+ะะฝ ะฟะพะดะบะปััะธั ะฒัะต ะผะฐัััััั ะทะฐะดะฐะฝะฝะพะณะพ ะผะฐัััััะธะทะฐัะพัะฐ ะบ ะฝะฐัะตะผั ะฟัะธะปะพะถะตะฝะธั.
+
+/// note | ะขะตั
ะฝะธัะตัะบะธะต ะดะตัะฐะปะธ
+
+ะคะฐะบัะธัะตัะบะธ, ะฒะฝัััะธ ะพะฝ ัะพะทะดะฐัั ะฒัะต *ะพะฟะตัะฐัะธะธ ะฟััะธ* ะดะปั ะบะฐะถะดะพะน ะพะฟะตัะฐัะธะธ ะฟััะธ ะพะฑััะฒะปะตะฝะฝะพะน ะฒ `APIRouter`.
+
+ะ ะฟะพะด ะบะฐะฟะพัะพะผ ะฒัั ะฑัะดะตั ัะฐะฑะพัะฐัั ัะฐะบ, ะบะฐะบ ะฑัะดัะพ ะฑั ะผั ะธะผะตะตะผ ะดะตะปะพ ั ะพะดะฝะธะผ ัะฐะนะปะพะผ ะฟัะธะปะพะถะตะฝะธั.
+
+///
+
+/// check | ะะฐะผะตัะบะฐ
+
+ะัะธ ะฟะพะดะบะปััะตะฝะธะธ ะผะฐัััััะธะทะฐัะพัะพะฒ ะฝะต ััะพะธั ะฑะตัะฟะพะบะพะธัััั ะพ ะฟัะพะธะทะฒะพะดะธัะตะปัะฝะพััะธ.
+
+ะะฟะตัะฐัะธั ะฟะพะดะบะปััะตะฝะธั ะทะฐะนะผัั ะผะธะบัะพัะตะบัะฝะดั ะธ ะฟะพะฝะฐะดะพะฑะธััั ัะพะปัะบะพ ะฟัะธ ะทะฐะฟััะบะต ะฟัะธะปะพะถะตะฝะธั.
+
+ะขะฐะบะธะผ ะพะฑัะฐะทะพะผ, ััะพ ะฝะต ะฟะพะฒะปะธัะตั ะฝะฐ ะฟัะพะธะทะฒะพะดะธัะตะปัะฝะพััั. โก
+
+///
+
+### ะะพะดะบะปััะตะฝะธะต `APIRouter` ั ะฟะพะปัะทะพะฒะฐัะตะปััะบะธะผะธ ะฟัะตัะธะบัะพะผ (`prefix`), ัะตะณะฐะผะธ (`tags`), ะพัะฒะตัะฐะผะธ (`responses`), ะธ ะทะฐะฒะธัะธะผะพัััะผะธ (`dependencies`)
+
+ะขะตะฟะตัั ะดะฐะฒะฐะนัะต ะฟัะตะดััะฐะฒะธะผ, ััะพ ะฒะฐัะฐ ะพัะณะฐะฝะธะทะฐัะธั ะฟะตัะตะดะฐะปะฐ ะฒะฐะผ ัะฐะนะป `app/internal/admin.py`.
+
+ะะฝ ัะพะดะตัะถะธั `APIRouter` ั ะฝะตะบะพัะพััะผะธ *ัะฝะดะฟะพะธัะฐะผะธ* ะฐะดะผะธะฝะธัััะธัะพะฒะฐะฝะธั, ะบะพัะพััะต ะฒะฐัะฐ ะพัะณะฐะฝะธะทะฐัะธั ะธัะฟะพะปัะทัะตั ะดะปั ะฝะตัะบะพะปัะบะธั
ะฟัะพะตะบัะพะฒ.
+
+ะ ะดะฐะฝะฝะพะผ ะฟัะธะผะตัะต ััะพ ัะดะตะปะฐัั ะพัะตะฝั ะฟัะพััะพ. ะะพ ะดะฐะฒะฐะนัะต ะฟัะตะดะฟะพะปะพะถะธะผ, ััะพ ะฟะพัะบะพะปัะบั ัะฐะนะป ะธัะฟะพะปัะทัะตััั ะดะปั ะฝะตัะบะพะปัะบะธั
ะฟัะพะตะบัะพะฒ,
+ัะพ ะผั ะฝะต ะผะพะถะตะผ ะผะพะดะธัะธัะธัะพะฒะฐัั ะตะณะพ, ะดะพะฑะฐะฒะปัั ะฟัะตัะธะบัั (`prefix`), ะทะฐะฒะธัะธะผะพััะธ (`dependencies`), ัะตะณะธ (`tags`), ะธ ั.ะด. ะฝะตะฟะพััะตะดััะฒะตะฝะฝะพ ะฒ `APIRouter`:
+
+```Python hl_lines="3" title="app/internal/admin.py"
+{!../../docs_src/bigger_applications/app/internal/admin.py!}
+```
+
+ะะพ, ะฝะตัะผะพััั ะฝะฐ ััะพ, ะผั ั
ะพัะธะผ ะธัะฟะพะปัะทะพะฒะฐัั ะบะฐััะพะผะฝัะน ะฟัะตัะธะบั (`prefix`) ะดะปั ะฟะพะดะบะปััะตะฝะฝะพะณะพ ะผะฐัััััะธะทะฐัะพัะฐ (`APIRouter`), ะฒ ัะตะทัะปััะฐัะต ัะตะณะพ, ะบะฐะถะดะฐั *ะพะฟะตัะฐัะธั ะฟััะธ* ะฑัะดะตั ะฝะฐัะธะฝะฐัััั ั `/admin`. ะขะฐะบะถะต ะผั ั
ะพัะธะผ ะทะฐัะธัะธัั ะฝะฐั ะผะฐัััััะธะทะฐัะพั ั ะฟะพะผะพััั ะทะฐะฒะธัะธะผะพััะตะน, ัะพะทะดะฐะฝะฝัั
ะดะปั ะฝะฐัะตะณะพ ะฟัะพะตะบัะฐ. ะ ะตัั ะผั ั
ะพัะธะผ ะฒะบะปััะธัั ัะตะณะธ (`tags`) ะธ ะพัะฒะตัั (`responses`).
+
+ะั ะผะพะถะตะผ ะฟัะธะผะตะฝะธัั ะฒัะต ะฒััะตะฟะตัะตัะธัะปะตะฝะฝัะต ะฝะฐัััะพะนะบะธ, ะฝะต ะธะทะผะตะฝัั ะฝะฐัะฐะปัะฝัะน `APIRouter`. ะะฐะผ ะฒัะตะณะพ ะปะธัั ะฝัะถะฝะพ ะฟะตัะตะดะฐัั ะฝัะถะฝัะต ะฟะฐัะฐะผะตััั ะฒ `app.include_router()`.
+
+```Python hl_lines="14-17" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+ะขะฐะบะธะผ ะพะฑัะฐะทะพะผ, ะพัะธะณะธะฝะฐะปัะฝัะน `APIRouter` ะฝะต ะฑัะดะตั ะผะพะดะธัะธัะธัะพะฒะฐะฝ, ะธ ะผั ัะผะพะถะตะผ ะธัะฟะพะปัะทะพะฒะฐัั ัะฐะนะป `app/internal/admin.py` ััะฐะทั ะฒ ะฝะตัะบะพะปัะบะธั
ะฟัะพะตะบัะฐั
ะพัะณะฐะฝะธะทะฐัะธะธ.
+
+ะ ัะตะทัะปััะฐัะต, ะฒ ะฝะฐัะตะผ ะฟัะธะปะพะถะตะฝะธะธ ะบะฐะถะดัะน *ัะฝะดะฟะพะธะฝั* ะผะพะดัะปั `admin` ะฑัะดะตั ะธะผะตัั:
+
+* ะัะตัะธะบั `/admin`.
+* ะขะตะณ `admin`.
+* ะะฐะฒะธัะธะผะพััั `get_token_header`.
+* ะัะฒะตั `418`. ๐ต
+
+ะญัะพ ะฑัะดะตั ะธะผะตัั ะผะตััะพ ะธัะบะปััะธัะตะปัะฝะพ ะดะปั `APIRouter` ะฒ ะฝะฐัะตะผ ะฟัะธะปะพะถะตะฝะธะธ, ะธ ะฝะต ะทะฐััะพะฝะตั ะปัะฑะพะน ะดััะณะพะน ะบะพะด, ะธัะฟะพะปัะทัััะธะน ะตะณะพ.
+
+ะะฐะฟัะธะผะตั, ะดััะณะธะต ะฟัะพะตะบัั, ะผะพะณัั ะธัะฟะพะปัะทะพะฒะฐัั ัะพั ะถะต ัะฐะผัะน `APIRouter` ั ะดััะณะธะผะธ ะผะตัะพะดะฐะผะธ ะฐััะตะฝัะธัะธะบะฐัะธะธ.
+
+### ะะพะดะบะปััะตะฝะธะต ะพัะดะตะปัะฝะพะณะพ *ัะฝะดะฟะพะธะฝัะฐ*
+
+ะั ัะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะธัั *ัะฝะดะฟะพะธะฝั* ะฝะตะฟะพััะตะดััะฒะตะฝะฝะพ ะฒ ะพัะฝะพะฒะฝะพะต ะฟัะธะปะพะถะตะฝะธะต `FastAPI`.
+
+ะะดะตัั ะผั ััะพ ะดะตะปะฐะตะผ ... ะฟัะพััะพ, ััะพะฑั ะฟะพะบะฐะทะฐัั, ััะพ ััะพ ะฒะพะทะผะพะถะฝะพ ๐คท:
+
+```Python hl_lines="21-23" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+ะธ ััะพ ะฑัะดะตั ัะฐะฑะพัะฐัั ะบะพััะตะบัะฝะพ ะฒะผะตััะต ั ะดััะณะธะผะธ *ัะฝะดะฟะพะธะฝัะฐะผะธ*, ะดะพะฑะฐะฒะปะตะฝะฝัะผะธ ั ะฟะพะผะพััั `app.include_router()`.
+
+/// info | ะกะปะพะถะฝัะต ัะตั
ะฝะธัะตัะบะธะต ะดะตัะฐะปะธ
+
+**ะัะธะผะตัะฐะฝะธะต**: ััะพ ัะปะพะถะฝะฐั ัะตั
ะฝะธัะตัะบะฐั ะดะตัะฐะปั, ะบะพัะพััั, ัะบะพัะตะต ะฒัะตะณะพ, **ะฒั ะผะพะถะตัะต ะฟัะพะฟัััะธัั**.
+
+---
+
+ะะฐัััััะธะทะฐัะพัั (`APIRouter`) ะฝะต "ะผะพะฝัะธัััััั" ะฟะพ-ะพัะดะตะปัะฝะพััะธ ะธ ะฝะต ะธะทะพะปะธัััััั ะพั ะพััะฐะปัะฝะพะณะพ ะฟัะธะปะพะถะตะฝะธั.
+
+ะญัะพ ะฟัะพะธัั
ะพะดะธั ะฟะพัะพะผั, ััะพ ะฝัะถะฝะพ ะฒะบะปััะธัั ะธั
*ัะฝะดะฟะพะธะฝัั* ะฒ OpenAPI ัั
ะตะผั ะธ ะฒ ะธะฝัะตััะตะนั ะฟะพะปัะทะพะฒะฐัะตะปั.
+
+ะ ัะธะปั ัะพะณะพ, ััะพ ะผั ะฝะต ะผะพะถะตะผ ะธั
ะธะทะพะปะธัะพะฒะฐัั ะธ "ะฟัะธะผะพะฝัะธัะพะฒะฐัั" ะฝะตะทะฐะฒะธัะธะผะพ ะพั ะพััะฐะปัะฝัั
, *ัะฝะดะฟะพะธะฝัั* ะบะปะพะฝะธัััััั (ะฟะตัะตัะพะทะดะฐัััั) ะธ ะฝะต ะฟะพะดะบะปััะฐัััั ะฝะฐะฟััะผัั.
+
+///
+
+## ะัะพะฒะตัะบะฐ ะฐะฒัะพะผะฐัะธัะตัะบะพะน ะดะพะบัะผะตะฝัะฐัะธะธ API
+
+ะขะตะฟะตัั ะทะฐะฟัััะธัะต ะฟัะธะปะพะถะตะฝะธะต:
+
+
+
+```console
+$ fastapi dev app/main.py
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+
+
+ะัะบัะพะนัะต ะดะพะบัะผะตะฝัะฐัะธั ะฟะพ ะฐะดัะตัั
http://127.0.0.1:8000/docs.
+
+ะั ัะฒะธะดะธัะต ะฐะฒัะพะผะฐัะธัะตัะบัั API ะดะพะบัะผะตะฝัะฐัะธั. ะะฝะฐ ะฒะบะปััะฐะตั ะฒ ัะตะฑั ะผะฐัััััั ะธะท ััะฑ-ะผะพะดัะปะตะน, ะธัะฟะพะปัะทัั ะฒะตัะฝัะต ะผะฐัััััั, ะฟัะตัะธะบัั ะธ ัะตะณะธ:
+
+

+
+## ะะพะดะบะปััะตะฝะธะต ัััะตััะฒัััะตะณะพ ะผะฐัััััะฐ ัะตัะตะท ะฝะพะฒัะน ะฟัะตัะธะบั (`prefix`)
+
+ะั ะผะพะถะตัะต ะธัะฟะพะปัะทะพะฒะฐัั `.include_router()` ะฝะตัะบะพะปัะบะพ ัะฐะท ั ะพะดะฝะธะผ ะธ ัะตะผ ะถะต ะผะฐัััััะพะผ, ะฟัะธะผะตะฝะธะฒ ัะฐะทะปะธัะฝัะต ะฟัะตัะธะบัั.
+
+ะญัะพ ะผะพะถะตั ะฑััั ะฟะพะปะตะทะฝัะผ, ะตัะปะธ ะฝัะถะฝะพ ะฟัะตะดะพััะฐะฒะธัั ะดะพัััะฟ ะบ ะพะดะฝะพะผั ะธ ัะพะผั ะถะต API ัะตัะตะท ัะฐะทะปะธัะฝัะต ะฟัะตัะธะบัั, ะฝะฐะฟัะธะผะตั, `/api/v1` ะธ `/api/latest`.
+
+ะญัะพ ะฟัะพะดะฒะธะฝัััะน ัะฟะพัะพะฑ, ะบะพัะพััะน ะฒะฐะผ ะผะพะถะตั ะธ ะฝะต ะฟัะธะณะพะดะธััั. ะั ะฟัะธะฒะพะดะธะผ ะตะณะพ ะฝะฐ ัะปััะฐะน, ะตัะปะธ ะฒะดััะณ ะฒะฐะผ ััะพ ะฟะพะฝะฐะดะพะฑะธััั.
+
+## ะะบะปััะตะฝะธะต ะพะดะฝะพะณะพ ะผะฐัััััะธะทะฐัะพัะฐ (`APIRouter`) ะฒ ะดััะณะพะน
+
+ะขะพัะฝะพ ัะฐะบ ะถะต, ะบะฐะบ ะฒั ะฒะบะปััะฐะตัะต `APIRouter` ะฒ ะฟัะธะปะพะถะตะฝะธะต `FastAPI`, ะฒั ะผะพะถะตัะต ะฒะบะปััะธัั `APIRouter` ะฒ ะดััะณะพะน `APIRouter`:
+
+```Python
+router.include_router(other_router)
+```
+
+ะฃะดะพััะพะฒะตัััะตัั, ััะพ ะฒั ัะดะตะปะฐะปะธ ััะพ ะดะพ ัะพะณะพ, ะบะฐะบ ะฟะพะดะบะปััะธัั ะผะฐัััััะธะทะฐัะพั (`router`) ะบ ะฒะฐัะตะผั `FastAPI` ะฟัะธะปะพะถะตะฝะธั, ะธ *ัะฝะดะฟะพะธะฝัั* ะผะฐัััััะธะทะฐัะพัะฐ `other_router` ะฑัะปะธ ัะฐะบะถะต ะฟะพะดะบะปััะตะฝั.
diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
index f9b9dec25..0e4eb95be 100644
--- a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
+++ b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -18,7 +18,7 @@
ะะฐะฒะธัะธะผะพััะธ ะธะท dependencies ะฒัะฟะพะปะฝัััั ัะฐะบ ะถะต, ะบะฐะบ ะธ ะพะฑััะฝัะต ะทะฐะฒะธัะธะผะพััะธ. ะะพ ะธั
ะทะฝะฐัะตะฝะธั (ะตัะปะธ ะพะฝะธ ะฑัะปะธ) ะฝะต ะฑัะดัั ะฟะตัะตะดะฐะฝั ะฒ *ััะฝะบัะธั ะพะฟะตัะฐัะธะธ ะฟััะธ*.
-/// ะฟะพะดัะบะฐะทะบะฐ
+/// tip | ะะพะดัะบะฐะทะบะฐ
ะะตะบะพัะพััะต ัะตะดะฐะบัะพัั ะบะพะดะฐ ะพะฟัะตะดะตะปััั ะฝะตะธัะฟะพะปัะทัะตะผัะต ะฟะฐัะฐะผะตััั ััะฝะบัะธะน ะธ ะฟะพะดัะฒะตัะธะฒะฐัั ะธั
ะบะฐะบ ะพัะธะฑะบั.
@@ -28,7 +28,7 @@
///
-/// ะดะพะฟะพะปะฝะธัะตะปัะฝะฐั | ะธะฝัะพัะผะฐัะธั
+/// info | ะัะธะผะตัะฐะฝะธะต
ะ ััะพะผ ะฟัะธะผะตัะต ะผั ะธัะฟะพะปัะทัะตะผ ะฒัะดัะผะฐะฝะฝัะต ะฟะพะปัะทะพะฒะฐัะตะปััะบะธะต ะทะฐะณะพะปะพะฒะบะธ `X-Key` ะธ `X-Token`.
diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md
index 32a98ff22..13b7015db 100644
--- a/docs/ru/docs/tutorial/query-params-str-validations.md
+++ b/docs/ru/docs/tutorial/query-params-str-validations.md
@@ -291,22 +291,6 @@ q: Union[str, None] = Query(default=None, min_length=3)
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
-### ะะฑัะทะฐัะตะปัะฝัะน ะฟะฐัะฐะผะตัั ั Ellipsis (`...`)
-
-ะะปััะตัะฝะฐัะธะฒะฝัะน ัะฟะพัะพะฑ ัะบะฐะทะฐัั ะพะฑัะทะฐัะตะปัะฝะพััั ะฟะฐัะฐะผะตััะฐ ะทะฐะฟัะพัะฐ - ััะพ ัะบะฐะทะฐัั ะฟะฐัะฐะผะตัั `default` ัะตัะตะท ะผะฝะพะณะพัะพัะธะต `...`:
-
-{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *}
-
-/// info | ะะพะฟะพะปะฝะธัะตะปัะฝะฐั ะธะฝัะพัะผะฐัะธั
-
-ะัะปะธ ะฒั ัะฐะฝะตะต ะฝะต ััะฐะปะบะธะฒะฐะปะธัั ั `...`: ััะพ ัะฟะตัะธะฐะปัะฝะพะต ะทะฝะฐัะตะฝะธะต,
ัะฐััั ัะทัะบะฐ Python ะธ ะฝะฐะทัะฒะฐะตััั "Ellipsis".
-
-ะัะฟะพะปัะทัะตััั ะฒ Pydantic ะธ FastAPI ะดะปั ะพะฟัะตะดะตะปะตะฝะธั, ััะพ ะทะฝะฐัะตะฝะธะต ััะตะฑัะตััั ะพะฑัะทะฐัะตะปัะฝะพ.
-
-///
-
-ะขะฐะบะธะผ ะพะฑัะฐะทะพะผ, **FastAPI** ะพะฟัะตะดะตะปัะตั, ััะพ ะฟะฐัะฐะผะตัั ัะฒะปัะตััั ะพะฑัะทะฐัะตะปัะฝัะผ.
-
### ะะฑัะทะฐัะตะปัะฝัะน ะฟะฐัะฐะผะตัั ั `None`
ะั ะผะพะถะตัะต ะพะฟัะตะดะตะปะธัั, ััะพ ะฟะฐัะฐะผะตัั ะผะพะถะตั ะฟัะธะฝะธะผะฐัั `None`, ะฝะพ ะฒัั ะตัั ัะฒะปัะตััั ะพะฑัะทะฐัะตะปัะฝัะผ. ะญัะพ ะผะพะถะตั ะฟะพััะตะฑะพะฒะฐัััั ะดะปั ัะพะณะพ, ััะพะฑั ะฟะพะปัะทะพะฒะฐัะตะปะธ ัะฒะฝะพ ัะบะฐะทะฐะปะธ ะฟะฐัะฐะผะตัั, ะดะฐะถะต ะตัะปะธ ะตะณะพ ะทะฝะฐัะตะฝะธะต ะฑัะดะตั `None`.
@@ -321,18 +305,6 @@ Pydantic, ะผะพัั ะบะพัะพัะพะณะพ ะธัะฟะพะปัะทัะตััั ะฒ FastAPI ะดะปั
///
-### ะัะฟะพะปัะทะพะฒะฐะฝะธะต Pydantic's `Required` ะฒะผะตััะพ Ellipsis (`...`)
-
-ะัะปะธ ะฒะฐั ัะผััะฐะตั `...`, ะฒั ะผะพะถะตัะต ะธัะฟะพะปัะทะพะฒะฐัั `Required` ะธะท Pydantic:
-
-{* ../../docs_src/query_params_str_validations/tutorial006d_an_py39.py hl[4,10] *}
-
-/// tip | ะะพะดัะบะฐะทะบะฐ
-
-ะะฐะฟะพะผะฝะธัะต, ะบะพะณะดะฐ ะฒะฐะผ ะฝะตะพะฑั
ะพะดะธะผะพ ะพะฑััะฒะธัั query-ะฟะฐัะฐะผะตัั ะพะฑัะทะฐัะตะปัะฝัะผ, ะฒั ะผะพะถะตัะต ะฟัะพััะพ ะฝะต ัะบะฐะทัะฒะฐัั ะฟะฐัะฐะผะตัั `default`. ะขะฐะบะธะผ ะพะฑัะฐะทะพะผ, ะฒะฐะผ ัะตะดะบะพ ะฟัะธะดัััั ะธัะฟะพะปัะทะพะฒะฐัั `...` ะธะปะธ `Required`.
-
-///
-
## ะะฝะพะถะตััะฒะพ ะทะฝะฐัะตะฝะธะน ะดะปั query-ะฟะฐัะฐะผะตััะฐ
ะะปั query-ะฟะฐัะฐะผะตััะฐ `Query` ะผะพะถะฝะพ ัะบะฐะทะฐัั, ััะพ ะพะฝ ะฟัะธะฝะธะผะฐะตั ัะฟะธัะพะบ ะทะฝะฐัะตะฝะธะน (ะผะฝะพะถะตััะฒะพ ะทะฝะฐัะตะฝะธะน).
diff --git a/docs/uk/docs/features.md b/docs/uk/docs/features.md
new file mode 100644
index 000000000..7d679d8ee
--- /dev/null
+++ b/docs/uk/docs/features.md
@@ -0,0 +1,189 @@
+# ะคัะฝะบััะพะฝะฐะปัะฝั ะผะพะถะปะธะฒะพััั
+
+## ะคัะฝะบััะพะฝะฐะปัะฝั ะผะพะถะปะธะฒะพััั FastAPI
+
+**FastAPI** ะฝะฐะดะฐั ะฒะฐะผ ัะฐะบั ะผะพะถะปะธะฒะพััั:
+
+### ะะธะบะพัะธััะฐะฝะฝั ะฒัะดะบัะธัะธั
ััะฐะฝะดะฐัััะฒ
+
+*
OpenAPI ะดะปั ััะฒะพัะตะฝะฝั API, ะฒะบะปััะฐััะธ ะพะณะพะปะพัะตะฝะฝั
ัะปัั
ัะฒ,
ะพะฟะตัะฐััะน, ะฟะฐัะฐะผะตัััะฒ, ััะป ะทะฐะฟะธััะฒ, ะฑะตะทะฟะตะบะธ ัะพัะพ.
+* ะะฒัะพะผะฐัะธัะฝะฐ ะดะพะบัะผะตะฝัะฐััั ะผะพะดะตะปะตะน ะดะฐะฝะธั
ะทะฐ ะดะพะฟะพะผะพะณะพั
JSON Schema (ะพัะบัะปัะบะธ OpenAPI ะฑะฐะทัััััั ัะฐะผะต ะฝะฐ JSON Schema).
+* ะ ะพะทัะพะฑะปะตะฝะพ ะฝะฐ ะพัะฝะพะฒั ัะธั
ััะฐะฝะดะฐัััะฒ ะฟััะปั ัะตัะตะปัะฝะพะณะพ ะฐะฝะฐะปัะทั, ะฐ ะฝะต ัะบ ะดะพะดะฐัะบะพะฒะธะน ััะฒะตะฝั ะฟะพะฒะตัั
ะพัะฝะพะฒะฝะพั ะฐัั
ััะตะบัััะธ.
+* ะฆะต ัะฐะบะพะถ ะดะฐั ะทะผะพะณั ะฐะฒัะพะผะฐัะธัะฝะพ **ะณะตะฝะตััะฒะฐัะธ ะบะพะด ะบะปััะฝัะฐ** ะฑะฐะณะฐััะผะฐ ะผะพะฒะฐะผะธ.
+
+### ะะฒัะพะผะฐัะธัะฝะฐ ะณะตะฝะตัะฐััั ะดะพะบัะผะตะฝัะฐััั
+
+ะะฝัะตัะฐะบัะธะฒะฝะฐ ะดะพะบัะผะตะฝัะฐััั API ัะฐ ะฒะตะฑัะฝัะตััะตะนั ะดะปั ะนะพะณะพ ะดะพัะปัะดะถะตะฝะฝั. ะัะบัะปัะบะธ ััะตะนะผะฒะพัะบ ะฑะฐะทัััััั ะฝะฐ OpenAPI, ั ะบัะปัะบะฐ ะฒะฐััะฐะฝััะฒ, ะดะฒะฐ ะท ัะบะธั
ะฒะบะปััะตะฝั ะทะฐ ะทะฐะผะพะฒััะฒะฐะฝะฝัะผ.
+
+*
Swagger UI โ ะดะพะทะฒะพะปัั ัะฝัะตัะฐะบัะธะฒะฝะพ ะฟะตัะตะณะปัะดะฐัะธ API, ะฒะธะบะปะธะบะฐัะธ ัะฐ ัะตัััะฒะฐัะธ ะนะพะณะพ ะฟััะผะพ ั ะฑัะฐัะทะตัั.
+
+
+
+* ะะปััะตัะฝะฐัะธะฒะฝะฐ ะดะพะบัะผะตะฝัะฐััั API ะทะฐ ะดะพะฟะพะผะพะณะพั
ReDoc.
+
+
+
+### ะขัะปัะบะธ ัััะฐัะฝะธะน Python
+
+FastAPI ะฒะธะบะพัะธััะพะฒัั ััะฐะฝะดะฐััะฝั **ัะธะฟะธ Python** (ะทะฐะฒะดัะบะธ Pydantic). ะะฐะผ ะฝะต ะฟะพัััะฑะฝะพ ะฒะธะฒัะฐัะธ ะฝะพะฒะธะน ัะธะฝัะฐะบัะธั โ ะปะธัะต ััะฐะฝะดะฐััะฝะธะน ัััะฐัะฝะธะน Python.
+
+ะฏะบัะพ ะฒะฐะผ ะฟะพัััะฑะฝะต ะบะพัะพัะบะต ะฝะฐะณะฐะดัะฒะฐะฝะฝั ะฟัะพ ะฒะธะบะพัะธััะฐะฝะฝั ัะธะฟัะฒ ั Python (ะฝะฐะฒััั ัะบัะพ ะฒะธ ะฝะต ะฒะธะบะพัะธััะพะฒัััะต FastAPI), ะฟะตัะตะณะปัะฝััะต ะบะพัะพัะบะธะน ะฟัะดัััะฝะธะบ: [ะัััะฟ ะดะพ ัะธะฟัะฒ Python](python-types.md){.internal-link target=_blank}.
+
+ะัั ะฟัะธะบะปะฐะด ััะฐะฝะดะฐััะฝะพะณะพ Python-ะบะพะดั ะท ัะธะฟะฐะผะธ:
+
+```Python
+from datetime import date
+from pydantic import BaseModel
+
+# ะะณะพะปะพัะตะฝะฝั ะทะผัะฝะฝะพั ัะบ str
+# ะท ะฟัะดััะธะผะบะพั ะฐะฒัะพะดะพะฟะพะฒะฝะตะฝะฝั ั ัะตะดะฐะบัะพัั
+def main(user_id: str):
+ return user_id
+
+# ะะพะดะตะปั Pydantic
+class User(BaseModel):
+ id: int
+ name: str
+ joined: date
+```
+
+ะัะธะบะปะฐะด ะฒะธะบะพัะธััะฐะฝะฝั ัััั ะผะพะดะตะปั:
+
+```Python
+my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
+
+second_user_data = {
+ "id": 4,
+ "name": "Mary",
+ "joined": "2018-11-30",
+}
+
+my_second_user: User = User(**second_user_data)
+```
+
+/// info | ะะฝัะพัะผะฐััั
+
+`**second_user_data` ะพะทะฝะฐัะฐั:
+
+ะะตัะตะดะฐัะธ ะบะปััั ัะฐ ะทะฝะฐัะตะฝะฝั ัะปะพะฒะฝะธะบะฐ `second_user_data` ัะบ ะฐัะณัะผะตะฝัะธ ั ะฒะธะณะปัะดั "ะบะปัั-ะทะฝะฐัะตะฝะฝั", ะตะบะฒัะฒะฐะปะตะฝัะฝะพ `User(id=4, name="Mary", joined="2018-11-30")`.
+
+///
+
+### ะัะดััะธะผะบะฐ ัะตะดะฐะบัะพััะฒ (IDE)
+
+ะคัะตะนะผะฒะพัะบ ัะฟัะพัะบัะพะฒะฐะฝะธะน ัะฐะบ, ัะพะฑ ะฑััะธ ะปะตะณะบะธะผ ั ัะฝััััะธะฒะฝะพ ะทัะพะทัะผัะปะธะผ. ะฃัั ัััะตะฝะฝั ัะตัััะฒะฐะปะธัั ั ััะทะฝะธั
ัะตะดะฐะบัะพัะฐั
ัะต ะดะพ ะฟะพัะฐัะบั ัะพะทัะพะฑะบะธ, ัะพะฑ ะทะฐะฑะตะทะฟะตัะธัะธ ะฝะฐะนะบัะฐัะธะน ะดะพัะฒัะด ะฟัะพะณัะฐะผัะฒะฐะฝะฝั.
+
+ะะฐ ัะตะทัะปััะฐัะฐะผะธ ะพะฟะธััะฒะฐะฝั ัะพะทัะพะฑะฝะธะบัะฒ Python
ะพะดะฝััั ะท ะฝะฐะนะฟะพะฟัะปััะฝััะธั
ััะฝะบััะน ั "ะฐะฒัะพะดะพะฟะพะฒะฝะตะฝะฝั".
+
+**FastAPI** ะฟะพะฒะฝัััั ะฟัะดััะธะผัั ะฐะฒัะพะดะพะฟะพะฒะฝะตะฝะฝั ั ะฒััั
ะผััััั
, ัะพะผั ะฒะฐะผ ััะดะบะพ ะดะพะฒะตะดะตัััั ะฟะพะฒะตััะฐัะธัั ะดะพ ะดะพะบัะผะตะฝัะฐััั.
+
+ะัะธะบะปะฐะด ะฐะฒัะพะดะพะฟะพะฒะฝะตะฝะฝั ั ัะตะดะฐะบัะพัะฐั
:
+
+* ั
Visual Studio Code:
+
+
+
+* ั
PyCharm:
+
+
+
+### ะะพัะพัะบะธะน ะบะพะด
+FastAPI ะผะฐั ัะพะทัะผะฝั ะฝะฐะปะฐัััะฒะฐะฝะฝั **ะทะฐ ะทะฐะผะพะฒััะฒะฐะฝะฝัะผ**, ะฐะปะต ะฒัั ะฟะฐัะฐะผะตััะธ ะผะพะถะฝะฐ ะฝะฐะปะฐััะพะฒัะฒะฐัะธ ะฒัะดะฟะพะฒัะดะฝะพ ะดะพ ะฒะฐัะธั
ะฟะพััะตะฑ. ะะดะฝะฐะบ ะทะฐ ะทะฐะผะพะฒััะฒะฐะฝะฝัะผ ะฒัะต "ะฟัะพััะพ ะฟัะฐััั".
+
+### ะะฐะปัะดะฐััั
+* ะัะดััะธะผะบะฐ ะฒะฐะปัะดะฐััั ะดะปั ะฑัะปััะพััั (ะฐะฑะพ ะฒััั
?) **ัะธะฟัะฒ ะดะฐะฝะธั
Python**, ะทะพะบัะตะผะฐ:
+ * JSON-ะพะฑ'ัะบััะฒ (`dict`).
+ * JSON-ัะฟะธัะบัะฒ (`list`) ะท ะฒะธะทะฝะฐัะตะฝะฝัะผ ัะธะฟัะฒ ะตะปะตะผะตะฝััะฒ.
+ * ะ ัะดะบัะฒ (`str`) ัะท ะผัะฝัะผะฐะปัะฝะพั ัะฐ ะผะฐะบัะธะผะฐะปัะฝะพั ะดะพะฒะถะธะฝะพั.
+ * ะงะธัะตะป (`int`, `float`) ะท ะพะฑะผะตะถะตะฝะฝัะผะธ ะผัะฝัะผะฐะปัะฝะธั
ัะฐ ะผะฐะบัะธะผะฐะปัะฝะธั
ะทะฝะฐัะตะฝั ัะพัะพ.
+
+* ะะฐะปัะดะฐััั ัะบะปะฐะดะฝััะธั
ัะธะฟัะฒ, ัะฐะบะธั
ัะบ:
+ * URL.
+ * Email.
+ * UUID.
+ * ...ัะฐ ัะฝัั.
+
+ะฃัั ะฒะฐะปัะดะฐััั ะฒะธะบะพะฝัััััั ัะตัะตะท ะฝะฐะดัะนะฝะธะน ัะฐ ะฟะตัะตะฒััะตะฝะธะน **Pydantic**.
+
+### ะะตะทะฟะตะบะฐ ัะฐ ะฐะฒัะตะฝัะธััะบะฐััั
+
+**FastAPI** ะฟัะดััะธะผัั ะฒะฑัะดะพะฒะฐะฝั ะฐะฒัะตะฝัะธััะบะฐััั ัะฐ ะฐะฒัะพัะธะทะฐััั, ะฑะตะท ะฟัะธะฒโัะทะบะธ ะดะพ ะบะพะฝะบัะตัะฝะธั
ะฑะฐะท ะดะฐะฝะธั
ัะธ ะผะพะดะตะปะตะน ะดะฐะฝะธั
.
+
+ะัะดััะธะผัััััั ะฒัั ัั
ะตะผะธ ะฑะตะทะฟะตะบะธ OpenAPI, ะฒะบะปััะฐััะธ:
+
+* HTTP Basic.
+* **OAuth2** (ัะฐะบะพะถ ัะท ะฟัะดััะธะผะบะพั **JWT-ัะพะบะตะฝัะฒ**). ะะธะฒ. ะฟัะดัััะฝะธะบ: [OAuth2 ัะท JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
+* ะะปััั API ะฒ:
+ * ะะฐะณะพะปะพะฒะบะฐั
.
+ * ะะฐัะฐะผะตััะฐั
ะทะฐะฟะธัั.
+ * Cookies ัะพัะพ.
+
+ะ ัะฐะบะพะถ ััั ะผะพะถะปะธะฒะพััั ะฑะตะทะฟะตะบะธ ะฒัะด Starlette (ะทะพะบัะตะผะฐ **ัะตััะนะฝั cookies**).
+
+ะฃัั ะฒะพะฝะธ ััะฒะพัะตะฝั ัะบ ะฑะฐะณะฐัะพัะฐะทะพะฒั ัะฝััััะผะตะฝัะธ ัะฐ ะบะพะผะฟะพะฝะตะฝัะธ, ัะบั ะปะตะณะบะพ ัะฝัะตะณััััััั ะท ะฒะฐัะธะผะธ ัะธััะตะผะฐะผะธ, ัั
ะพะฒะธัะฐะผะธ ะดะฐะฝะธั
, ัะตะปัััะนะฝะธะผะธ ัะฐ NoSQL ะฑะฐะทะฐะผะธ ะดะฐะฝะธั
ัะพัะพ.
+
+### ะะฟัะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพััะตะน
+
+**FastAPI** ะผัััะธัั ะฝะฐะดะทะฒะธัะฐะนะฝะพ ะฟัะพััั ั ะฒะธะบะพัะธััะฐะฝะฝั, ะฐะปะต ะฟะพััะถะฝั ัะธััะตะผั ะฒะฟัะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพััะตะน.
+
+* ะะฐะปะตะถะฝะพััั ะผะพะถััั ะผะฐัะธ ะฒะปะฐัะฝั ะทะฐะปะตะถะฝะพััั, ััะฒะพััััะธ ัััะฐัั
ัั ะฐะฑะพ **"ะณัะฐั ะทะฐะปะตะถะฝะพััะตะน"**.
+* ะฃัั ะทะฐะปะตะถะฝะพััั ะฐะฒัะพะผะฐัะธัะฝะพ ะบะตััััััั ััะตะนะผะฒะพัะบะพะผ.
+* ะฃัั ะทะฐะปะตะถะฝะพััั ะผะพะถััั ะพััะธะผัะฒะฐัะธ ะดะฐะฝั ะท ะทะฐะฟะธััะฒ ั ัะพะทัะธััะฒะฐัะธ **ะพะฑะผะตะถะตะฝะฝั ะพะฟะตัะฐััั ะทะฐ ัะปัั
ะพะผ** ัะฐ ะฐะฒัะพะผะฐัะธัะฝั ะดะพะบัะผะตะฝัะฐััั.
+* **ะะฒัะพะผะฐัะธัะฝะฐ ะฒะฐะปัะดะฐััั** ะฝะฐะฒััั ะดะปั ะฟะฐัะฐะผะตัััะฒ *ะพะฟะตัะฐััะน ัะปัั
ั*, ะฒะธะทะฝะฐัะตะฝะธั
ั ะทะฐะปะตะถะฝะพัััั
.
+* ะัะดััะธะผะบะฐ ัะบะปะฐะดะฝะธั
ัะธััะตะผ ะฐะฒัะตะฝัะธััะบะฐััั ะบะพัะธัััะฒะฐััะฒ, **ะท'ัะดะฝะฐะฝั ัะท ะฑะฐะทะฐะผะธ ะดะฐะฝะธั
** ัะพัะพ.
+* **ะะพะดะฝะธั
ะพะฑะผะตะถะตะฝั** ัะพะดะพ ะฒะธะบะพัะธััะฐะฝะฝั ะฑะฐะท ะดะฐะฝะธั
, ััะพะฝัะตะฝะดัะฒ ัะพัะพ, ะฐะปะต ะฒะพะดะฝะพัะฐั ะฟัะพััะฐ ัะฝัะตะณัะฐััั ะท ัััะผะฐ ะฝะธะผะธ.
+
+### ะะตะผะฐั ะพะฑะผะตะถะตะฝั ะฝะฐ "ะฟะปะฐะณัะฝะธ"
+
+ะะฑะพ ัะฝัะธะผะธ ัะปะพะฒะฐะผะธ, ะฒะพะฝะธ ะฝะต ะฟะพัััะฑะฝั โ ะฟัะพััะพ ัะผะฟะพัััะนัะต ัะฐ ะฒะธะบะพัะธััะพะฒัะนัะต ะฝะตะพะฑั
ัะดะฝะธะน ะบะพะด.
+
+ะัะดั-ัะบะฐ ัะฝัะตะณัะฐััั ัะฟัะพัะบัะพะฒะฐะฝะฐ ะฝะฐัััะปัะบะธ ะฟัะพััะพ (ะท ะฒะธะบะพัะธััะฐะฝะฝัะผ ะทะฐะปะตะถะฝะพััะตะน), ัะพ ะฒะธ ะผะพะถะตัะต ััะฒะพัะธัะธ "ะฟะปะฐะณัะฝ" ะดะปั ัะฒะพะณะพ ะทะฐััะพััะฝะบั ะฒััะพะณะพ ั 2 ััะดะบะฐั
ะบะพะดั, ะฒะธะบะพัะธััะพะฒัััะธ ัั ัะฐะผั ััััะบัััั ัะฐ ัะธะฝัะฐะบัะธั, ัะพ ะน ะดะปั ะฒะฐัะธั
*ะพะฟะตัะฐััะน ัะปัั
ั*.
+
+### ะัะพัะตััะพะฒะฐะฝะพ
+
+* 100%
ะฟะพะบัะธััั ัะตััะฐะผะธ.
+* 100%
ะฐะฝะพัะพะฒะฐะฝะฐ ัะธะฟะฐะผะธ ะบะพะดะพะฒะฐ ะฑะฐะทะฐ.
+* ะะธะบะพัะธััะพะฒัััััั ั ัะพะฑะพัะธั
ัะตัะตะดะพะฒะธัะฐั
.
+
+## ะะพะถะปะธะฒะพััั Starlette
+
+**FastAPI** ะฟะพะฒะฝัััั ััะผััะฝะธะน ัะท (ัะฐ ะฟะพะฑัะดะพะฒะฐะฝะธะน ะฝะฐ ะพัะฝะพะฒั)
Starlette. ะขะพะผั ะฑัะดั-ัะบะธะน ะดะพะดะฐัะบะพะฒะธะน ะบะพะด Starlette, ัะบะธะน ะฒะธ ะผะฐััะต, ัะฐะบะพะถ ะฟัะฐััะฒะฐัะธะผะต.
+
+**FastAPI** ัะฐะบัะธัะฝะพ ั ะฟัะดะบะปะฐัะพะผ **Starlette**. ะขะพะผั, ัะบัะพ ะฒะธ ะฒะถะต ะทะฝะฐะนะพะผั ะทั Starlette ะฐะฑะพ ะฒะธะบะพัะธััะพะฒัััะต ะนะพะณะพ, ะฑัะปัััััั ััะฝะบััะพะฝะฐะปัะฝะพััั ะฟัะฐััะฒะฐัะธะผะต ัะฐะบ ัะฐะผะพ.
+
+ะ **FastAPI** ะฒะธ ะพััะธะผัััะต ะฒัั ะผะพะถะปะธะฒะพััั **Starlette** (ะฐะดะถะต FastAPI โ ัะต, ะฟะพ ัััั, Starlette ะฝะฐ ััะตัะพัะดะฐั
):
+
+* ะ ะฐะทััะฐ ะฟัะพะดัะบัะธะฒะฝัััั. ะฆะต
ะพะดะธะฝ ัะท ะฝะฐะนัะฒะธะดัะธั
ััะตะนะผะฒะพัะบัะฒ ะฝะฐ Python, ะฝะฐ ััะฒะฝั ะท **NodeJS** ั **Go**.
+* ะัะดััะธะผะบะฐ **WebSocket**.
+* ะคะพะฝะพะฒั ะทะฐะดะฐัั ั ะฟัะพัะตัั.
+* ะะพะดัั ะทะฐะฟััะบั ัะฐ ะทะฐะฒะตััะตะฝะฝั ัะพะฑะพัะธ.
+* ะะปััะฝั ะดะปั ัะตัััะฒะฐะฝะฝั, ะฟะพะฑัะดะพะฒะฐะฝะธะน ะฝะฐ HTTPX.
+* ะัะดััะธะผะบะฐ **CORS**, **GZip**, ััะฐัะธัะฝะธั
ัะฐะนะปัะฒ, ะฟะพัะพะบะพะฒะธั
ะฒัะดะฟะพะฒัะดะตะน.
+* ะัะดััะธะผะบะฐ **ัะตััะน** ั **cookie**.
+* 100% ะฟะพะบัะธััั ัะตััะฐะผะธ.
+* 100% ะฐะฝะพัะพะฒะฐะฝะฐ ัะธะฟะฐะผะธ ะบะพะดะพะฒะฐ ะฑะฐะทะฐ.
+
+## ะะพะถะปะธะฒะพััั Pydantic
+
+**FastAPI** ะฟะพะฒะฝัััั ััะผััะฝะธะน ัะท (ัะฐ ะฟะพะฑัะดะพะฒะฐะฝะธะน ะฝะฐ ะพัะฝะพะฒั)
Pydantic. ะขะพะผั ะฑัะดั-ัะบะธะน ะดะพะดะฐัะบะพะฒะธะน ะบะพะด Pydantic, ัะบะธะน ะฒะธ ะผะฐััะต, ัะฐะบะพะถ ะฟัะฐััะฒะฐัะธะผะต.
+
+ะะบะปััะฐััะธ ะทะพะฒะฝััะฝั ะฑัะฑะปัะพัะตะบะธ, ะฟะพะฑัะดะพะฒะฐะฝั ัะฐะบะพะถ ะฝะฐ Pydantic, ัะฐะบั ัะบ
ORM,
ODM ะดะปั ะฑะฐะท ะดะฐะฝะธั
.
+
+ะฆะต ัะฐะบะพะถ ะพะทะฝะฐัะฐั, ัะพ ะฒ ะฑะฐะณะฐััะพั
ะฒะธะฟะฐะดะบะฐั
ะฒะธ ะผะพะถะตัะต ะฟะตัะตะดะฐัะธ ัะพะน ัะฐะผะธะน ะพะฑ'ัะบั, ัะบะธะน ะพััะธะผัััะต ะท ะทะฐะฟะธัั, **ะฑะตะทะฟะพัะตัะตะดะฝัะพ ะฒ ะฑะฐะทั ะดะฐะฝะธั
**, ะพัะบัะปัะบะธ ะฒัะต ะฐะฒัะพะผะฐัะธัะฝะพ ะฟะตัะตะฒัััััััั.
+
+ะขะต ะถ ัะฐะผะต ะฒัะดะฑัะฒะฐััััั ะน ั ะทะฒะพัะพัะฝะพะผั ะฝะฐะฟััะผะบั โ ั ะฑะฐะณะฐััะพั
ะฒะธะฟะฐะดะบะฐั
ะฒะธ ะผะพะถะตัะต ะฟัะพััะพ ะฟะตัะตะดะฐัะธ ะพะฑ'ัะบั, ัะบะธะน ะพััะธะผัััะต ะท ะฑะฐะทะธ ะดะฐะฝะธั
, **ะฑะตะทะฟะพัะตัะตะดะฝัะพ ะบะปััะฝัั**.
+
+ะ **FastAPI** ะฒะธ ะพััะธะผัััะต ะฒัั ะผะพะถะปะธะฒะพััั **Pydantic** (ะฐะดะถะต FastAPI ะฑะฐะทัััััั ะฝะฐ Pydantic ะดะปั ะพะฑัะพะฑะบะธ ะฒััั
ะดะฐะฝะธั
):
+
+* **ะััะบะพั ะฟะปััะฐะฝะธะฝะธ** :
+ * ะะต ะฟะพัััะฑะฝะพ ะฒัะธัะธ ะฝะพะฒั ะผะพะฒั ะดะปั ะฒะธะทะฝะฐัะตะฝะฝั ัั
ะตะผ.
+ * ะฏะบัะพ ะฒะธ ะทะฝะฐััะต ัะธะฟะธ Python, ะฒะธ ะทะฝะฐััะต, ัะบ ะฒะธะบะพัะธััะพะฒัะฒะฐัะธ Pydantic.
+* ะะตะณะบะพ ะฟัะฐััั ะท ะฒะฐัะธะผ **
IDE/
ะปัะฝัะตัะพะผ/ะผะพะทะบะพะผ**:
+ * ะัะบัะปัะบะธ ััััะบัััะธ ะดะฐะฝะธั
Pydantic ั ะฟัะพััะพ ะตะบะทะตะผะฟะปััะฐะผะธ ะบะปะฐััะฒ, ัะบั ะฒะธ ะฒะธะทะฝะฐัะฐััะต; ะฐะฒัะพะดะพะฟะพะฒะฝะตะฝะฝั, ะปัะฝัะธะฝะณ, mypy ั ะฒะฐัะฐ ัะฝัััััั ะฟะพะฒะธะฝะฝั ะดะพะฑัะต ะฟัะฐััะฒะฐัะธ ะท ะฒะฐัะธะผะธ ะฟะตัะตะฒััะตะฝะธะผะธ ะดะฐะฝะธะผะธ.
+* ะะฐะปัะดะฐััั **ัะบะปะฐะดะฝะธั
ััััะบััั**:
+ * ะะธะบะพัะธััะฐะฝะฝั ัััะฐัั
ััะฝะธั
ะผะพะดะตะปะตะน Pydantic. Python `typing`, `List` ั `Dict` ัะพัะพ.
+ * ะะฐะปัะดะฐัะพัะธ ะดะพะทะฒะพะปัััั ัััะบะพ ั ะฟัะพััะพ ะฒะธะทะฝะฐัะฐัะธ, ะฟะตัะตะฒััััะธ ะน ะดะพะบัะผะตะฝััะฒะฐัะธ ัะบะปะฐะดะฝั ัั
ะตะผะธ ะดะฐะฝะธั
ั ะฒะธะณะปัะดั JSON-ัั
ะตะผะธ.
+ * ะะธ ะผะพะถะตัะต ะผะฐัะธ ะณะปะธะฑะพะบะพ **ะฒะบะปะฐะดะตะฝั JSON ะพะฑ'ัะบัะธ** ัะฐ ะฟะตัะตะฒััะธัะธ ัะฐ ะฐะฝะพััะฒะฐัะธ ัั
ะฒัั.
+* **ะ ะพะทัะธััะฒะฐะฝัััั**:
+ * Pydantic ะดะพะทะฒะพะปัั ะฒะธะทะฝะฐัะฐัะธ ะบะพัะธัััะฒะฐััะบั ัะธะฟะธ ะดะฐะฝะธั
ะฐะฑะพ ัะพะทัะธััะฒะฐัะธ ะฒะฐะปัะดะฐััั ะผะตัะพะดะฐะผะธ ะฒ ะผะพะดะตะปั ะดะตะบะพัะฐัะพัะพะผ `validator`.
+* 100% ะฟะพะบัะธััั ัะตััะฐะผะธ.
diff --git a/docs/uk/docs/learn/index.md b/docs/uk/docs/learn/index.md
new file mode 100644
index 000000000..7f9f21e57
--- /dev/null
+++ b/docs/uk/docs/learn/index.md
@@ -0,0 +1,5 @@
+# ะะฐะฒัะฐะฝะฝั
+
+ะฃ ััะพะผั ัะพะทะดัะปั ะฝะฐะดะฐะฝั ะฒัััะฟะฝั ัะฐ ะฝะฐะฒัะฐะปัะฝั ะผะฐัะตััะฐะปะธ ะดะปั ะฒะธะฒัะตะฝะฝั FastAPI.
+
+ะฆะต ะผะพะถะฝะฐ ัะพะทะณะปัะดะฐัะธ ัะบ **ะบะฝะธะณั**, **ะบััั**, ะฐะฑะพ **ะพััััะนะฝะธะน** ัะฐ ัะตะบะพะผะตะฝะดะพะฒะฐะฝะธะน ัะฟะพััะฑ ะพัะฒะพััะธ FastAPI. ๐
diff --git a/docs/uk/docs/tutorial/static-files.md b/docs/uk/docs/tutorial/static-files.md
new file mode 100644
index 000000000..a84782d8f
--- /dev/null
+++ b/docs/uk/docs/tutorial/static-files.md
@@ -0,0 +1,40 @@
+# ะกัะฐัะธัะฝั ัะฐะนะปะธ
+
+ะะธ ะผะพะถะตัะต ะฐะฒัะพะผะฐัะธัะฝะพ ะฝะฐะดะฐะฒะฐัะธ ััะฐัะธัะฝั ัะฐะนะปะธ ะท ะบะฐัะฐะปะพะณั, ะฒะธะบะพัะธััะพะฒัััะธ `StaticFiles`.
+
+## ะะธะบะพัะธััะฐะฝะฝั `StaticFiles`
+
+* ะะผะฟะพัััะนัะต `StaticFiles`.
+* "ะัะด'ัะดะฝะฐัะธ" ะตะบะทะตะผะฟะปัั `StaticFiles()` ะท ะฒะบะฐะทะฐะฝะฝัะผ ะฝะตะพะฑั
ัะดะฝะพะณะพ ัะปัั
ั.
+
+{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
+
+/// note | ะขะตั
ะฝััะฝั ะดะตัะฐะปั
+
+ะะธ ัะฐะบะพะถ ะผะพะถะตัะต ะฒะธะบะพัะธััะพะฒัะฒะฐัะธ `from starlette.staticfiles import StaticFiles`.
+
+**FastAPI** ะฝะฐะดะฐั ัะพะน ัะฐะผะธะน `starlette.staticfiles`, ัะพ ะน `fastapi.staticfiles` ะดะปั ะทัััะฝะพััั ัะพะทัะพะฑะฝะธะบัะฒ. ะะปะต ัะฐะบัะธัะฝะพ ะฒัะฝ ะฑะตะทะฟะพัะตัะตะดะฝัะพ ะฟะพั
ะพะดะธัั ัะท Starlette.
+
+///
+
+### ะฉะพ ัะฐะบะต "ะัะด'ัะดะฝะฐะฝะฝั"
+
+"ะัะด'ัะดะฝะฐะฝะฝั" ะพะทะฝะฐัะฐั ะดะพะดะฐะฒะฐะฝะฝั ะฟะพะฒะฝะพััะฝะฝะพะณะพ "ะฝะตะทะฐะปะตะถะฝะพะณะพ" ะทะฐััะพััะฝะบั ะทะฐ ะฟะตะฒะฝะธะผ ัะปัั
ะพะผ, ัะบะธะน ะฟะพััะผ ะพะฑัะพะฑะปัั ะฒัั ะฟัะด ัะปัั
ะธ.
+
+ะฆะต ะฒัะดััะทะฝัััััั ะฒัะด ะฒะธะบะพัะธััะฐะฝะฝั `APIRouter`, ะพัะบัะปัะบะธ ะฟัะด'ัะดะฝะฐะฝะธะน ะทะฐััะพััะฝะพะบ ั ะฟะพะฒะฝัััั ะฝะตะทะฐะปะตะถะฝะธะผ. OpenAPI ัะฐ ะดะพะบัะผะตะฝัะฐััั ะฒะฐัะพะณะพ ะพัะฝะพะฒะฝะพะณะพ ะทะฐััะพััะฝะบั ะฝะต ะฑัะดััั ะทะฝะฐัะธ ะฝััะพะณะพ ะฟัะพ ะฒะฐั ะฟัะด'ัะดะฝะฐะฝะธะน ะทะฐััะพััะฝะพะบ.
+
+ะะธ ะผะพะถะตัะต ะดัะทะฝะฐัะธัั ะฑัะปััะต ะฟัะพ ัะต ะฒ [ะะพััะฑะฝะธะบั ะดะปั ะฟัะพััะฝััะธั
ะบะพัะธัััะฒะฐััะฒ](../advanced/index.md){.internal-link target=_blank}.
+
+## ะะตัะฐะปั
+
+ะะตััะต `"/static"` ะฒะบะฐะทัั ะฝะฐ ะฟัะด ัะปัั
, ะทะฐ ัะบะธะผ ะฑัะดะต "ะฟัะด'ัะดะฝะฐะฝะพ" ัะตะน ะฝะพะฒะธะน "ะทะฐััะพััะฝะพะบ". ะขะพะผั ะฑัะดั-ัะบะธะน ัะปัั
, ัะบะธะน ะฟะพัะธะฝะฐััััั ะท `"/static"`, ะฑัะดะต ะพะฑัะพะฑะปััะธัั ะฝะธะผ.
+
+`directory="static"` ะฒะธะทะฝะฐัะฐั ะบะฐัะฐะปะพะณ, ัะพ ะผัััะธัั ะฒะฐัั ััะฐัะธัะฝั ัะฐะนะปะธ.
+
+`name="static"` ัะต ัะผ'ั, ัะบะต ะผะพะถะฝะฐ ะฒะธะบะพัะธััะพะฒัะฒะฐัะธ ะฒัะตัะตะดะธะฝั **FastAPI**.
+
+ะฃัั ัั ะฟะฐัะฐะผะตััะธ ะผะพะถััั ะฑััะธ ะทะผัะฝะตะฝั ะฒัะดะฟะพะฒัะดะฝะพ ะดะพ ะฟะพััะตะฑ ั ะพัะพะฑะปะธะฒะพััะตะน ะฒะฐัะพะณะพ ะทะฐััะพััะฝะบั.
+
+## ะะพะดะฐัะบะพะฒะฐ ัะฝัะพัะผะฐััั
+
+ะะตัะฐะปัะฝััะต ะฟัะพ ะฝะฐะปะฐัััะฒะฐะฝะฝั ัะฐ ะผะพะถะปะธะฒะพััั ะผะพะถะฝะฐ ะดัะทะฝะฐัะธัั ะฒ
ะดะพะบัะผะตะฝัะฐััั Starlette ะฟัะพ ััะฐัะธัะฝั ัะฐะนะปะธ.
diff --git a/docs/vi/docs/environment-variables.md b/docs/vi/docs/environment-variables.md
new file mode 100644
index 000000000..dd06f8959
--- /dev/null
+++ b/docs/vi/docs/environment-variables.md
@@ -0,0 +1,300 @@
+# Biแบฟn mรดi trฦฐแปng (Environment Variables)
+
+/// tip
+
+Nแบฟu bแบกn ฤรฃ biแบฟt vแป "biแบฟn mรดi trฦฐแปng" vร cรกch sแปญ dแปฅng chรบng, bแบกn cรณ thแป bแป qua phแบงn nร y.
+
+///
+
+Mแปt biแบฟn mรดi trฦฐแปng (cรฒn ฤฦฐแปฃc gแปi lร "**env var**") lร mแปt biแบฟn mร tแปn tแบกi **bรชn ngoร i** ฤoแบกn mรฃ Python, แป trong **hแป ฤiแปu hร nh**, vร cรณ thแป ฤฦฐแปฃc ฤแปc bแปi ฤoแบกn mรฃ Python cแปงa bแบกn (hoแบทc bแปi cรกc chฦฐฦกng trรฌnh khรกc).
+
+Cรกc biแบฟn mรดi trฦฐแปng cรณ thแป ฤฦฐแปฃc sแปญ dแปฅng ฤแป xแปญ lรญ **cรกc thiแบฟt lแบญp** cแปงa แปฉng dแปฅng, nhฦฐ mแปt phแบงn cแปงa **cรกc quรก trรฌnh cร i ฤแบทt** Python, v.v.
+
+## Tแบกo vร Sแปญ dแปฅng cรกc Biแบฟn Mรดi Trฦฐแปng
+
+Bแบกn cรณ thแป **tแบกo** vร sแปญ dแปฅng cรกc biแบฟn mรดi trฦฐแปng trong **shell (terminal)**, mร khรดng cแบงn sแปญ dแปฅng Python:
+
+//// tab | Linux, macOS, Windows Bash
+
+
+
+```console
+// Bแบกn cรณ thแป tแบกo mแปt biแบฟn mรดi trฦฐแปng MY_NAME vแปi
+$ export MY_NAME="Wade Wilson"
+
+// Sau ฤรณ bแบกn cรณ thแป sแปญ dแปฅng nรณ vแปi cรกc chฦฐฦกng trรฌnh khรกc, nhฦฐ
+$ echo "Hello $MY_NAME"
+
+Hello Wade Wilson
+```
+
+
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+```console
+// Tแบกo mแปt biแบฟn mรดi trฦฐแปng MY_NAME
+$ $Env:MY_NAME = "Wade Wilson"
+
+// Sแปญ dแปฅng nรณ vแปi cรกc chฦฐฦกng trรฌnh khรกc, nhฦฐ lร
+$ echo "Hello $Env:MY_NAME"
+
+Hello Wade Wilson
+```
+
+
+
+////
+
+## ฤแปc cรกc Biแบฟn Mรดi Trฦฐแปng trong Python
+
+Bแบกn cลฉng cรณ thแป tแบกo cรกc biแบฟn mรดi trฦฐแปng **bรชn ngoร i** ฤoแบกn mรฃ Python, trong terminal (hoแบทc bแบฑng bแบฅt kแปณ phฦฐฦกng phรกp nร o khรกc), vร sau ฤรณ **ฤแปc chรบng trong Python**.
+
+Vรญ dแปฅ, bแบกn cรณ mแปt file `main.py` vแปi:
+
+```Python hl_lines="3"
+import os
+
+name = os.getenv("MY_NAME", "World")
+print(f"Hello {name} from Python")
+```
+
+/// tip
+
+Tham sแป thแปฉ hai cho
`os.getenv()` lร giรก trแป mแบทc ฤแปnh ฤแป trแบฃ vแป.
+
+Nแบฟu khรดng ฤฦฐแปฃc cung cแบฅp, nรณ mแบทc ฤแปnh lร `None`, แป ฤรขy chรบng ta cung cแบฅp `"World"` lร giรก trแป mแบทc ฤแปnh ฤแป sแปญ dแปฅng.
+
+///
+
+Sau ฤรณ bแบกn cรณ thแป gแปi chฦฐฦกng trรฌnh Python:
+
+//// tab | Linux, macOS, Windows Bash
+
+
+
+```console
+// แป ฤรขy chรบng ta chฦฐa cร i ฤแบทt biแบฟn mรดi trฦฐแปng
+$ python main.py
+
+// Vรฌ chรบng ta chฦฐa cร i ฤแบทt biแบฟn mรดi trฦฐแปng, chรบng ta nhแบญn ฤฦฐแปฃc giรก trแป mแบทc ฤแปnh
+
+Hello World from Python
+
+// Nhฦฐng nแบฟu chรบng ta tแบกo mแปt biแบฟn mรดi trฦฐแปng trฦฐแปc ฤรณ
+$ export MY_NAME="Wade Wilson"
+
+// Vร sau ฤรณ gแปi chฦฐฦกng trรฌnh lแบกi
+$ python main.py
+
+// Bรขy giแป nรณ cรณ thแป ฤแปc biแบฟn mรดi trฦฐแปng
+
+Hello Wade Wilson from Python
+```
+
+
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+```console
+// แป ฤรขy chรบng ta chฦฐa cร i ฤแบทt biแบฟn mรดi trฦฐแปng
+$ python main.py
+
+// Vรฌ chรบng ta chฦฐa cร i ฤแบทt biแบฟn mรดi trฦฐแปng, chรบng ta nhแบญn ฤฦฐแปฃc giรก trแป mแบทc ฤแปnh
+
+Hello World from Python
+
+// Nhฦฐng nแบฟu chรบng ta tแบกo mแปt biแบฟn mรดi trฦฐแปng trฦฐแปc ฤรณ
+$ $Env:MY_NAME = "Wade Wilson"
+
+// Vร sau ฤรณ gแปi chฦฐฦกng trรฌnh lแบกi
+$ python main.py
+
+// Bรขy giแป nรณ cรณ thแป ฤแปc biแบฟn mรดi trฦฐแปng
+
+Hello Wade Wilson from Python
+```
+
+
+
+////
+
+Vรฌ cรกc biแบฟn mรดi trฦฐแปng cรณ thแป ฤฦฐแปฃc tแบกo bรชn ngoร i ฤoแบกn mรฃ Python, nhฦฐng cรณ thแป ฤฦฐแปฃc ฤแปc bแปi ฤoแบกn mรฃ Python, vร khรดng cแบงn ฤฦฐแปฃc lฦฐu trแปฏ (commit vร o `git`) cรนng vแปi cรกc file khรกc, nรชn chรบng thฦฐแปng ฤฦฐแปฃc sแปญ dแปฅng ฤแป lฦฐu cรกc thiแบฟt lแบญp hoแบทc **cแบฅu hรฌnh**.
+
+Bแบกn cลฉng cรณ thแป tแบกo ra mแปt biแบฟn mรดi trฦฐแปng dร nh riรชng cho mแปt **lแบงn gแปi chฦฐฦกng trรฌnh**, chแป cรณ thแป ฤฦฐแปฃc sแปญ dแปฅng bแปi chฦฐฦกng trรฌnh ฤรณ, vร chแป trong thแปi gian chแบกy cแปงa chฦฐฦกng trรฌnh.
+
+ฤแป lร m ฤiแปu nร y, tแบกo nรณ ngay trฦฐแปc chฦฐฦกng trรฌnh ฤรณ, trรชn cรนng mแปt dรฒng:
+
+
+
+```console
+// Tแบกo mแปt biแบฟn mรดi trฦฐแปng MY_NAME cho lแบงn gแปi chฦฐฦกng trรฌnh nร y
+$ MY_NAME="Wade Wilson" python main.py
+
+// Bรขy giแป nรณ cรณ thแป ฤแปc biแบฟn mรดi trฦฐแปng
+
+Hello Wade Wilson from Python
+
+// Biแบฟn mรดi trฦฐแปng khรดng cรฒn tแปn tแบกi sau ฤรณ
+$ python main.py
+
+Hello World from Python
+```
+
+
+
+/// tip
+
+Bแบกn cรณ thแป ฤแปc thรชm vแป ฤiแปu nร y tแบกi
The Twelve-Factor App: Config.
+
+///
+
+## Cรกc Kiแปu (Types) vร Kiแปm tra (Validation)
+
+Cรกc biแบฟn mรดi trฦฐแปng cรณ thแป chแป xแปญ lรญ **chuแปi kรฝ tแปฑ**, vรฌ chรบng nแบฑm bรชn ngoร i ฤoแบกn mรฃ Python vร phแบฃi tฦฐฦกng thรญch vแปi cรกc chฦฐฦกng trรฌnh khรกc vร phแบงn cรฒn lแบกi cแปงa hแป thแปng (vร thแบญm chรญ vแปi cรกc hแป ฤiแปu hร nh khรกc, nhฦฐ Linux, Windows, macOS).
+
+ฤiแปu nร y cรณ nghฤฉa lร **bแบฅt kแปณ giรก trแป nร o** ฤฦฐแปฃc ฤแปc trong Python tแปซ mแปt biแบฟn mรดi trฦฐแปng **sแบฝ lร mแปt `str`**, vร bแบฅt kแปณ hร nh ฤแปng chuyแปn ฤแปi sang kiแปu dแปฏ liแปu khรกc hoแบทc hร nh ฤแปng kiแปm tra nร o cลฉng phแบฃi ฤฦฐแปฃc thแปฑc hiแปn trong ฤoแบกn mรฃ.
+
+Bแบกn sแบฝ hแปc thรชm vแป viแปc sแปญ dแปฅng biแบฟn mรดi trฦฐแปng ฤแป xแปญ lรญ **cรกc thiแบฟt lแบญp แปฉng dแปฅng** trong [Hฦฐแปng dแบซn nรขng cao - Cรกc thiแบฟt lแบญp vร biแบฟn mรดi trฦฐแปng](./advanced/settings.md){.internal-link target=_blank}.
+
+## Biแบฟn mรดi trฦฐแปng `PATH`
+
+Cรณ mแปt biแบฟn mรดi trฦฐแปng **ฤแบทc biแปt** ฤฦฐแปฃc gแปi lร **`PATH`** ฤฦฐแปฃc sแปญ dแปฅng bแปi cรกc hแป ฤiแปu hร nh (Linux, macOS, Windows) nhแบฑm tรฌm cรกc chฦฐฦกng trรฌnh ฤแป thแปฑc thi.
+
+Giรก trแป cแปงa biแบฟn mรดi trฦฐแปng `PATH` lร mแปt chuแปi dร i ฤฦฐแปฃc tแบกo bแปi cรกc thฦฐ mแปฅc ฤฦฐแปฃc phรขn tรกch bแปi dแบฅu hai chแบฅm `:` trรชn Linux vร macOS, vร bแปi dแบฅu chแบฅm phแบฉy `;` trรชn Windows.
+
+Vรญ dแปฅ, biแบฟn mรดi trฦฐแปng `PATH` cรณ thแป cรณ dแบกng nhฦฐ sau:
+
+//// tab | Linux, macOS
+
+```plaintext
+/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+```
+
+ฤiแปu nร y cรณ nghฤฉa lร hแป thแปng sแบฝ tรฌm kiแบฟm cรกc chฦฐฦกng trรฌnh trong cรกc thฦฐ mแปฅc:
+
+* `/usr/local/bin`
+* `/usr/bin`
+* `/bin`
+* `/usr/sbin`
+* `/sbin`
+
+////
+
+//// tab | Windows
+
+```plaintext
+C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
+```
+
+ฤiแปu nร y cรณ nghฤฉa lร hแป thแปng sแบฝ tรฌm kiแบฟm cรกc chฦฐฦกng trรฌnh trong cรกc thฦฐ mแปฅc:
+
+* `C:\Program Files\Python312\Scripts`
+* `C:\Program Files\Python312`
+* `C:\Windows\System32`
+
+////
+
+Khi bแบกn gรต mแปt **lแปnh** trong terminal, hแป ฤiแปu hร nh **tรฌm kiแบฟm** chฦฐฦกng trรฌnh trong **mแปi thฦฐ mแปฅc** ฤฦฐแปฃc liแปt kรช trong biแบฟn mรดi trฦฐแปng `PATH`.
+
+Vรญ dแปฅ, khi bแบกn gรต `python` trong terminal, hแป ฤiแปu hร nh tรฌm kiแบฟm mแปt chฦฐฦกng trรฌnh ฤฦฐแปฃc gแปi `python` trong **thฦฐ mแปฅc ฤแบงu tiรชn** trong danh sรกch ฤรณ.
+
+Nแบฟu tรฌm thแบฅy, nรณ sแบฝ **sแปญ dแปฅng** nรณ. Nแบฟu khรดng tรฌm thแบฅy, nรณ sแบฝ tiแบฟp tแปฅc tรฌm kiแบฟm trong **cรกc thฦฐ mแปฅc khรกc**.
+
+### Cร i ฤแบทt Python vร cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH`
+
+Khi bแบกn cร i ฤแบทt Python, bแบกn cรณ thแป ฤฦฐแปฃc hแปi nแบฟu bแบกn muแปn cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH`.
+
+//// tab | Linux, macOS
+
+Giแบฃ sแปญ bแบกn cร i ฤแบทt Python vร o thฦฐ mแปฅc `/opt/custompython/bin`.
+
+Nแบฟu bแบกn chแปn cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH`, thรฌ cร i ฤแบทt sแบฝ thรชm `/opt/custompython/bin` vร o biแบฟn mรดi trฦฐแปng `PATH`.
+
+Nรณ cรณ thแป cรณ dแบกng nhฦฐ sau:
+
+```plaintext
+/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
+```
+
+Nhฦฐ vแบญy, khi bแบกn gรต `python` trong terminal, hแป thแปng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong `/opt/custompython/bin` (thฦฐ mแปฅc cuแปi) vร sแปญ dแปฅng nรณ.
+
+////
+
+//// tab | Windows
+
+Giแบฃ sแปญ bแบกn cร i ฤแบทt Python vร o thฦฐ mแปฅc `C:\opt\custompython\bin`.
+
+Nแบฟu bแบกn chแปn cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH`, thรฌ cร i ฤแบทt sแบฝ thรชm `C:\opt\custompython\bin` vร o biแบฟn mรดi trฦฐแปng `PATH`.
+
+Nรณ cรณ thแป cรณ dแบกng nhฦฐ sau:
+
+```plaintext
+C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
+```
+
+Nhฦฐ vแบญy, khi bแบกn gรต `python` trong terminal, hแป thแปng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong `C:\opt\custompython\bin` (thฦฐ mแปฅc cuแปi) vร sแปญ dแปฅng nรณ.
+
+////
+
+Vแบญy, nแบฟu bแบกn gรต:
+
+
+
+```console
+$ python
+```
+
+
+
+//// tab | Linux, macOS
+
+Hแป thแปng sแบฝ **tรฌm kiแบฟm** chฦฐฦกng trรฌnh `python` trong `/opt/custompython/bin` vร thแปฑc thi nรณ.
+
+Nรณ tฦฐฦกng ฤฦฐฦกng vแปi viแปc bแบกn gรต:
+
+
+
+```console
+$ /opt/custompython/bin/python
+```
+
+
+
+////
+
+//// tab | Windows
+
+Hแป thแปng sแบฝ **tรฌm kiแบฟm** chฦฐฦกng trรฌnh `python` trong `C:\opt\custompython\bin\python` vร thแปฑc thi nรณ.
+
+Nรณ tฦฐฦกng ฤฦฐฦกng vแปi viแปc bแบกn gรต:
+
+
+
+```console
+$ C:\opt\custompython\bin\python
+```
+
+
+
+////
+
+Thรดng tin nร y sแบฝ hแปฏu รญch khi bแบกn hแปc vแป [Mรดi trฦฐแปng แบฃo](virtual-environments.md){.internal-link target=_blank}.
+
+## Kแบฟt luแบญn
+
+Vแปi nhแปฏng thรดng tin nร y, bแบกn cรณ thแป hiแปu ฤฦฐแปฃc **cรกc biแบฟn mรดi trฦฐแปng lร gรฌ** vร **cรกch sแปญ dแปฅng chรบng trong Python**.
+
+Bแบกn cรณ thแป ฤแปc thรชm vแป chรบng tแบกi
Wikipedia cho Biแบฟn mรดi trฦฐแปng.
+
+Trong nhiแปu trฦฐแปng hแปฃp, cรกch cรกc biแบฟn mรดi trฦฐแปng trแป nรชn hแปฏu รญch vร cรณ thแป รกp dแปฅng khรดng thแปฑc sแปฑ rรต rร ng ngay tแปซ ฤแบงu, nhฦฐng chรบng sแบฝ liรชn tแปฅc xuแบฅt hiแปn trong rแบฅt nhiแปu tรฌnh huแปng khi bแบกn phรกt triแปn แปฉng dแปฅng, vรฌ vแบญy viแปc hiแปu biแบฟt vแป chรบng lร hแปฏu รญch.
+
+Chแบณng hแบกn, bแบกn sแบฝ cแบงn nhแปฏng thรดng tin nร y khi bแบกn hแปc vแป [Mรดi trฦฐแปng แบฃo](virtual-environments.md).
diff --git a/docs/vi/docs/fastapi-cli.md b/docs/vi/docs/fastapi-cli.md
new file mode 100644
index 000000000..d9e315ae4
--- /dev/null
+++ b/docs/vi/docs/fastapi-cli.md
@@ -0,0 +1,75 @@
+# FastAPI CLI
+
+**FastAPI CLI** lร mแปt chฦฐฦกng trรฌnh dรฒng lแปnh cรณ thแป ฤฦฐแปฃc sแปญ dแปฅng ฤแป phแปฅc vแปฅ แปฉng dแปฅng FastAPI cแปงa bแบกn, quแบฃn lรฝ dแปฑ รกn FastAPI cแปงa bแบกn vร nhiแปu hoแบกt ฤแปng khรกc.
+
+Khi bแบกn cร i ฤแบทt FastAPI (vd vแปi `pip install "fastapi[standard]"`), nรณ sแบฝ bao gแปm mแปt gรณi ฤฦฐแปฃc gแปi lร `fastapi-cli`, gรณi nร y cung cแบฅp lแปnh `fastapi` trong terminal.
+
+ฤแป chแบกy แปฉng dแปฅng FastAPI cแปงa bแบกn cho quรก trรฌnh phรกt triแปn (development), bแบกn cรณ thแป sแปญ dแปฅng lแปnh `fastapi dev`:
+
+
+
+```console
+$ fastapi dev main.py
+
+ FastAPI Starting development server ๐
+
+ Searching for package file structure from directories with
+ __init__.py files
+ Importing from /home/user/code/awesomeapp
+
+ module ๐ main.py
+
+ code Importing the FastAPI app object from the module with the
+ following code:
+
+ from main import app
+
+ app Using import string: main:app
+
+ server Server started at http://127.0.0.1:8000
+ server Documentation at http://127.0.0.1:8000/docs
+
+ tip Running in development mode, for production use:
+ fastapi run
+
+ Logs:
+
+ INFO Will watch for changes in these directories:
+ ['/home/user/code/awesomeapp']
+ INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to
+ quit)
+ INFO Started reloader process [383138] using WatchFiles
+ INFO Started server process [383153]
+ INFO Waiting for application startup.
+ INFO Application startup complete.
+```
+
+
+
+Chฦฐฦกng trรฌnh dรฒng lแปnh `fastapi` lร **FastAPI CLI**.
+
+FastAPI CLI nhแบญn ฤฦฐแปng dแบซn ฤแบฟn chฦฐฦกng trรฌnh Python cแปงa bแบกn (vd `main.py`) vร tแปฑ ฤแปng phรกt hiแปn ฤแปi tฦฐแปฃng `FastAPI` (thฦฐแปng ฤฦฐแปฃc gแปi lร `app`), xรกc ฤแปnh quรก trรฌnh nhแบญp ฤรบng, vร sau ฤรณ chแบกy nรณ (serve).
+
+ฤแปi vแปi vแบญn hร nh thแปฑc tแบฟ (production), bแบกn sแบฝ sแปญ dแปฅng `fastapi run` thay thแบฟ. ๐
+
+แป bรชn trong, **FastAPI CLI** sแปญ dแปฅng
Uvicorn, mแปt server ASGI cรณ hiแปu suแบฅt cao, sแบตn sร ng cho vแบญn hร nh thแปฑc tแบฟ (production). ๐
+
+## `fastapi dev`
+
+Chแบกy `fastapi dev` sแบฝ khแปi ฤแปng quรก trรฌnh phรกt triแปn.
+
+Mแบทc ฤแปnh, **auto-reload** ฤฦฐแปฃc bแบญt, tแปฑ ฤแปng tแบฃi lแบกi server khi bแบกn thay ฤแปi code cแปงa bแบกn. ฤiแปu nร y tแปn nhiแปu tร i nguyรชn vร cรณ thแป kรฉm แปn ฤแปnh hฦกn khi nรณ bแป tแบฏt. Bแบกn nรชn sแปญ dแปฅng nรณ cho quรก trรฌnh phรกt triแปn. Nรณ cลฉng lแบฏng nghe ฤแปa chแป IP `127.0.0.1`, ฤรณ lร ฤแปa chแป IP cแปงa mรกy tรญnh ฤแป tแปฑ giao tiแบฟp vแปi chรญnh nรณ (`localhost`).
+
+## `fastapi run`
+
+Chแบกy `fastapi run` mแบทc ฤแปnh sแบฝ khแปi ฤแปng FastAPI cho quรก trรฌnh vแบญn hร nh thแปฑc tแบฟ.
+
+Mแบทc ฤแปnh, **auto-reload** bแป tแบฏt. Nรณ cลฉng lแบฏng nghe ฤแปa chแป IP `0.0.0.0`, ฤรณ lร tแบฅt cแบฃ cรกc ฤแปa chแป IP cรณ sแบตn, nhฦฐ vแบญy nรณ sแบฝ ฤฦฐแปฃc truy cแบญp cรดng khai bแปi bแบฅt kแปณ ai cรณ thแป giao tiแบฟp vแปi mรกy tรญnh. ฤรขy lร cรกch bแบกn thฦฐแปng chแบกy nรณ trong sแบฃn phแบฉm hoร n thiแปn, vรญ dแปฅ trong mแปt container.
+
+Trong hแบงu hแบฟt cรกc trฦฐแปng hแปฃp, bแบกn sแบฝ (vร nรชn) cรณ mแปt "proxy ฤiแปm cuแปi (termination proxy)" xแปญ lรฝ HTTPS cho bแบกn, ฤiแปu nร y sแบฝ phแปฅ thuแปc vร o cรกch bแบกn triแปn khai แปฉng dแปฅng cแปงa bแบกn, nhร cung cแบฅp cรณ thแป lร m ฤiแปu nร y cho bแบกn, hoแบทc bแบกn cรณ thแป cแบงn thiแบฟt lแบญp nรณ.
+
+/// tip
+
+Bแบกn cรณ thแป tรฌm hiแปu thรชm vแป FastAPI CLI trong [tร i liแปu triแปn khai](deployment/index.md){.internal-link target=_blank}.
+
+///
diff --git a/docs/vi/docs/tutorial/static-files.md b/docs/vi/docs/tutorial/static-files.md
new file mode 100644
index 000000000..ecf8c2485
--- /dev/null
+++ b/docs/vi/docs/tutorial/static-files.md
@@ -0,0 +1,40 @@
+# Tแปp tฤฉnh (Static Files)
+
+Bแบกn cรณ thแป triแปn khai tแปp tฤฉnh tแปฑ ฤแปng tแปซ mแปt thฦฐ mแปฅc bแบฑng cรกch sแปญ dแปฅng StaticFiles.
+
+## Sแปญ dแปฅng `Tแปp tฤฉnh`
+
+- Nhแบญp `StaticFiles`.
+- "Mount" a `StaticFiles()` instance in a specific path.
+
+{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
+
+/// note | Chi tiแบฟt kแปน thuแบญt
+
+Bแบกn cลฉng cรณ thแป sแปญ dแปฅng `from starlette.staticfiles import StaticFiles`.
+
+**FastAPI** cung cแบฅp cรนng `starlette.staticfiles` nhฦฐ `fastapi.staticfiles` giรบp ฤฦกn giแบฃn hรณa viแปc sแปญ dแปฅng, nhฦฐng nรณ thแปฑc sแปฑ ฤแบฟn tแปซ Starlette.
+
+///
+
+### "Mounting" lร gรฌ
+
+"Mounting" cรณ nghฤฉa lร thรชm mแปt แปฉng dแปฅng "ฤแปc lแบญp" hoร n chแปnh vร o mแปt ฤฦฐแปng dแบซn cแปฅ thแป, sau ฤรณ แปฉng dแปฅng ฤรณ sแบฝ chแปu trรกch nhiแปm xแปญ lรฝ tแบฅt cแบฃ cรกc ฤฦฐแปng dแบซn con.
+
+ฤiแปu nร y khรกc vแปi viแปc sแปญ dแปฅng `APIRouter` vรฌ mแปt แปฉng dแปฅng ฤฦฐแปฃc gแบฏn kแบฟt lร hoร n toร n ฤแปc lแบญp. OpenAPI vร tร i liแปu tแปซ แปฉng dแปฅng chรญnh cแปงa bแบกn sแบฝ khรดng bao gแปm bแบฅt kแปณ thแปฉ gรฌ tแปซ แปฉng dแปฅng ฤฦฐแปฃc gแบฏn kแบฟt, v.v.
+
+Bแบกn cรณ thแป ฤแปc thรชm vแป ฤiแปu nร y trong [Hฦฐแปng dแบซn Ngฦฐแปi dรนng Nรขng cao](../advanced/index.md){.internal-link target=\_blank}.
+
+## Chi tiแบฟt
+
+ฤฦฐแปng dแบซn ฤแบงu tiรชn `"/static"` lร ฤฦฐแปng dแบซn con mร "แปฉng dแปฅng con" nร y sแบฝ ฤฦฐแปฃc "gแบฏn" vร o. Vรฌ vแบญy, bแบฅt kแปณ ฤฦฐแปng dแบซn nร o bแบฏt ฤแบงu bแบฑng `"/static"` sแบฝ ฤฦฐแปฃc xแปญ lรฝ bแปi nรณ.
+
+ฤฦฐแปng dแบซn `directory="static"` lร tรชn cแปงa thฦฐ mแปฅc chแปฉa tแปp tฤฉnh cแปงa bแบกn.
+
+Tham sแป `name="static"` ฤแบทt tรชn cho nรณ ฤแป cรณ thแป ฤฦฐแปฃc sแปญ dแปฅng bรชn trong **FastAPI**.
+
+Tแบฅt cแบฃ cรกc tham sแป nร y cรณ thแป khรกc vแปi `static`, ฤiแปu chแปnh chรบng vแปi phรน hแปฃp vแปi แปฉng dแปฅng cแปงa bแบกn.
+
+## Thรดng tin thรชm
+
+ฤแป biแบฟt thรชm chi tiแบฟt vร tรนy chแปn, hรฃy xem
Starlette's docs about Static Files.
diff --git a/docs/vi/docs/virtual-environments.md b/docs/vi/docs/virtual-environments.md
new file mode 100644
index 000000000..22d8e153e
--- /dev/null
+++ b/docs/vi/docs/virtual-environments.md
@@ -0,0 +1,842 @@
+# Mรดi trฦฐแปng แบฃo (Virtual Environments)
+
+Khi bแบกn lร m viแปc trong cรกc dแปฑ รกn Python, bแบกn cรณ thแป sแปญ dแปฅng mแปt **mรดi trฦฐแปng แบฃo** (hoแบทc mแปt cฦก chแบฟ tฦฐฦกng tแปฑ) ฤแป cรกch ly cรกc gรณi bแบกn cร i ฤแบทt cho mแปi dแปฑ รกn.
+
+/// info
+Nแบฟu bแบกn ฤรฃ biแบฟt vแป cรกc mรดi trฦฐแปng แบฃo, cรกch tแบกo chรบng vร sแปญ dแปฅng chรบng, bแบกn cรณ thแป bแป qua phแบงn nร y. ๐ค
+
+///
+
+/// tip
+
+Mแปt **mรดi trฦฐแปng แบฃo** khรกc vแปi mแปt **biแบฟn mรดi trฦฐแปng (environment variable)**.
+
+Mแปt **biแบฟn mรดi trฦฐแปng** lร mแปt biแบฟn trong hแป thแปng cรณ thแป ฤฦฐแปฃc sแปญ dแปฅng bแปi cรกc chฦฐฦกng trรฌnh.
+
+Mแปt **mรดi trฦฐแปng แบฃo** lร mแปt thฦฐ mแปฅc vแปi mแปt sแป tแปp trong ฤรณ.
+
+///
+
+/// info
+
+Trang nร y sแบฝ hฦฐแปng dแบซn bแบกn cรกch sแปญ dแปฅng cรกc **mรดi trฦฐแปng แบฃo** vร cรกch chรบng hoแบกt ฤแปng.
+
+Nแบฟu bแบกn ฤรฃ sแบตn sร ng sแปญ dแปฅng mแปt **cรดng cแปฅ cรณ thแป quแบฃn lรฝ tแบฅt cแบฃ mแปi thแปฉ** cho bแบกn (bao gแปm cแบฃ viแปc cร i ฤแบทt Python), hรฃy thแปญ
uv.
+
+///
+
+## Tแบกo mแปt Dแปฑ รกn
+
+ฤแบงu tiรชn, tแบกo mแปt thฦฐ mแปฅc cho dแปฑ รกn cแปงa bแบกn.
+
+Cรกch tรดi thฦฐแปng lร m lร tแบกo mแปt thฦฐ mแปฅc cรณ tรชn `code` trong thฦฐ mแปฅc `home/user`.
+
+Vร trong thฦฐ mแปฅc ฤรณ, tรดi tแบกo mแปt thฦฐ mแปฅc cho mแปi dแปฑ รกn.
+
+
+
+```console
+// ฤi ฤแบฟn thฦฐ mแปฅc home
+$ cd
+// Tแบกo mแปt thฦฐ mแปฅc cho tแบฅt cแบฃ cรกc dแปฑ รกn cแปงa bแบกn
+$ mkdir code
+// Vร o thฦฐ mแปฅc code
+$ cd code
+// Tแบกo mแปt thฦฐ mแปฅc cho dแปฑ รกn nร y
+$ mkdir awesome-project
+// Vร o thฦฐ mแปฅc dแปฑ รกn
+$ cd awesome-project
+```
+
+
+
+## Tแบกo mแปt Mรดi trฦฐแปng แบฃo
+
+Khi bแบกn bแบฏt ฤแบงu lร m viแปc vแปi mแปt dแปฑ รกn Python **trong lแบงn ฤแบงu**, hรฃy tแบกo mแปt mรดi trฦฐแปng แบฃo **
trong thฦฐ mแปฅc dแปฑ รกn cแปงa bแบกn**.
+
+/// tip
+
+Bแบกn cแบงn lร m ฤiแปu nร y **mแปt lแบงn cho mแปi dแปฑ รกn**, khรดng phแบฃi mแปi khi bแบกn lร m viแปc.
+///
+
+//// tab | `venv`
+
+ฤแป tแบกo mแปt mรดi trฦฐแปng แบฃo, bแบกn cรณ thแป sแปญ dแปฅng module `venv` cรณ sแบตn cแปงa Python.
+
+
+
+```console
+$ python -m venv .venv
+```
+
+
+
+/// details | Cรกch cรกc lแปnh hoแบกt ฤแปng
+
+* `python`: sแปญ dแปฅng chฦฐฦกng trรฌnh `python`
+* `-m`: gแปi mแปt module nhฦฐ mแปt script, chรบng ta sแบฝ nรณi vแป module ฤรณ sau
+* `venv`: sแปญ dแปฅng module `venv` ฤฦฐแปฃc cร i ฤแบทt sแบตn cแปงa Python
+* `.venv`: tแบกo mรดi trฦฐแปng แบฃo trong thฦฐ mแปฅc mแปi `.venv`
+
+///
+
+////
+
+//// tab | `uv`
+
+Nแบฟu bแบกn cรณ
`uv` ฤฦฐแปฃc cร i ฤแบทt, bแบกn cรณ thแป sแปญ dแปฅng nรณ ฤแป tแบกo mแปt mรดi trฦฐแปng แบฃo.
+
+
+
+```console
+$ uv venv
+```
+
+
+
+/// tip
+
+Mแบทc ฤแปnh, `uv` sแบฝ tแบกo mแปt mรดi trฦฐแปng แบฃo trong mแปt thฦฐ mแปฅc cรณ tรชn `.venv`.
+
+Nhฦฐng bแบกn cรณ thแป tรนy chแปnh nรณ bแบฑng cรกch thรชm mแปt ฤแปi sแป vแปi tรชn thฦฐ mแปฅc.
+
+///
+
+////
+
+Lแปnh nร y tแบกo mแปt mรดi trฦฐแปng แบฃo mแปi trong mแปt thฦฐ mแปฅc cรณ tรชn `.venv`.
+
+/// details | `.venv` hoแบทc tรชn khรกc
+
+Bแบกn cรณ thแป tแบกo mรดi trฦฐแปng แบฃo trong mแปt thฦฐ mแปฅc khรกc, nhฦฐng thฦฐแปng ngฦฐแปi ta quy ฦฐแปc ฤแบทt nรณ lร `.venv`.
+
+///
+
+## Kรญch hoแบกt Mรดi trฦฐแปng แบฃo
+
+Kรญch hoแบกt mรดi trฦฐแปng แบฃo mแปi ฤแป bแบฅt kแปณ lแปnh Python nร o bแบกn chแบกy hoแบทc gรณi nร o bแบกn cร i ฤแบทt sแบฝ sแปญ dแปฅng nรณ.
+
+/// tip
+
+Lร m ฤiแปu nร y **mแปi khi** bแบกn bแบฏt ฤแบงu mแปt **phiรชn terminal mแปi** ฤแป lร m viแปc trรชn dแปฑ รกn.
+
+///
+
+//// tab | Linux, macOS
+
+
+
+```console
+$ source .venv/bin/activate
+```
+
+
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+```console
+$ .venv\Scripts\Activate.ps1
+```
+
+
+
+////
+
+//// tab | Windows Bash
+
+Nแบฟu bแบกn sแปญ dแปฅng Bash cho Windows (vรญ dแปฅ:
Git Bash):
+
+
+
+```console
+$ source .venv/Scripts/activate
+```
+
+
+
+////
+
+/// tip
+
+Mแปi khi bแบกn cร i ฤแบทt thรชm mแปt **package mแปi** trong mรดi trฦฐแปng ฤรณ, hรฃy **kรญch hoแบกt** mรดi trฦฐแปng ฤรณ lแบกi.
+
+ฤiแปu nร y ฤแบฃm bแบฃo rแบฑng khi bแบกn sแปญ dแปฅng mแปt **chฦฐฦกng trรฌnh dรฒng lแปnh (
CLI)** ฤฦฐแปฃc cร i ฤแบทt tแปซ gรณi ฤรณ, bแบกn sแบฝ dรนng bแบฃn cร i ฤแบทt tแปซ mรดi trฦฐแปng แบฃo cแปงa mรฌnh thay vรฌ bแบฃn ฤฦฐแปฃc cร i ฤแบทt toร n cแปฅc khรกc cรณ thแป cรณ phiรชn bแบฃn khรกc vแปi phiรชn bแบฃn bแบกn cแบงn.
+
+///
+
+## Kiแปm tra xem Mรดi trฦฐแปng แบฃo ฤรฃ ฤฦฐแปฃc Kรญch hoแบกt chฦฐa
+
+Kiแปm tra xem mรดi trฦฐแปng แบฃo ฤรฃ ฤฦฐแปฃc kรญch hoแบกt chฦฐa (lแปnh trฦฐแปc ฤรณ ฤรฃ hoแบกt ฤแปng).
+
+/// tip
+
+ฤiแปu nร y lร **khรดng bแบฏt buแปc**, nhฦฐng nรณ lร mแปt cรกch tแปt ฤแป **kiแปm tra** rแบฑng mแปi thแปฉ ฤang hoแบกt ฤแปng nhฦฐ mong ฤแปฃi vร bแบกn ฤang sแปญ dแปฅng ฤรบng mรดi trฦฐแปng แบฃo mร bแบกn ฤรฃ ฤแปnh.
+
+///
+
+//// tab | Linux, macOS, Windows Bash
+
+
+
+```console
+$ which python
+
+/home/user/code/awesome-project/.venv/bin/python
+```
+
+
+
+Nแบฟu nรณ hiแปn thแป `python` binary tแบกi `.venv/bin/python`, trong dแปฑ รกn cแปงa bแบกn (trong trฦฐแปng hแปฃp `awesome-project`), thรฌ tแปฉc lร nรณ hoแบกt ฤแปng. ๐
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+```console
+$ Get-Command python
+
+C:\Users\user\code\awesome-project\.venv\Scripts\python
+```
+
+
+
+Nแบฟu nรณ hiแปn thแป `python` binary tแบกi `.venv\Scripts\python`, trong dแปฑ รกn cแปงa bแบกn (trong trฦฐแปng hแปฃp `awesome-project`), thรฌ tแปฉc lร nรณ hoแบกt ฤแปng. ๐
+
+////
+
+## Nรขng cแบฅp `pip`
+
+/// tip
+
+Nแบฟu bแบกn sแปญ dแปฅng
`uv` bแบกn sแปญ dแปฅng nรณ ฤแป cร i ฤแบทt thay vรฌ `pip`, thรฌ bแบกn khรดng cแบงn cแบญp nhแบญt `pip`. ๐
+
+///
+
+Nแบฟu bแบกn sแปญ dแปฅng `pip` ฤแป cร i ฤแบทt gรณi (nรณ ฤฦฐแปฃc cร i ฤแบทt mแบทc ฤแปnh vแปi Python), bแบกn nรชn **nรขng cแบฅp** nรณ lรชn phiรชn bแบฃn mแปi nhแบฅt.
+
+Nhiแปu lแปi khรกc nhau trong khi cร i ฤแบทt gรณi ฤฦฐแปฃc giแบฃi quyแบฟt chแป bแบฑng cรกch nรขng cแบฅp `pip` trฦฐแปc.
+
+/// tip
+
+Bแบกn thฦฐแปng lร m ฤiแปu nร y **mแปt lแบงn**, ngay sau khi bแบกn tแบกo mรดi trฦฐแปng แบฃo.
+
+///
+
+ฤแบฃm bแบฃo rแบฑng mรดi trฦฐแปng แบฃo ฤรฃ ฤฦฐแปฃc kรญch hoแบกt (vแปi lแปnh trรชn) vร sau ฤรณ chแบกy:
+
+
+
+```console
+$ python -m pip install --upgrade pip
+
+---> 100%
+```
+
+
+
+## Thรชm `.gitignore`
+
+Nแบฟu bแบกn sแปญ dแปฅng **Git** (nรชn lร m), hรฃy thรชm mแปt file `.gitignore` ฤแป Git bแป qua mแปi thแปฉ trong `.venv`.
+
+/// tip
+
+Nแบฟu bแบกn sแปญ dแปฅng
`uv` ฤแป tแบกo mรดi trฦฐแปng แบฃo, nรณ ฤรฃ tแปฑ ฤแปng lร m ฤiแปu nร y cho bแบกn, bแบกn cรณ thแป bแป qua bฦฐแปc nร y. ๐
+
+///
+
+/// tip
+
+Lร m ฤiแปu nร y **mแปt lแบงn**, ngay sau khi bแบกn tแบกo mรดi trฦฐแปng แบฃo.
+
+///
+
+
+
+```console
+$ echo "*" > .venv/.gitignore
+```
+
+
+
+/// details | Cรกch lแปnh hoแบกt ฤแปng
+
+* `echo "*"`: sแบฝ "in" vฤn bแบฃn `*` trong terminal (phแบงn tiแบฟp theo sแบฝ thay ฤแปi ฤiแปu ฤรณ mแปt chรบt)
+* `>`: bแบฅt kแปณ vฤn bแบฃn nร o ฤฦฐแปฃc in ra terminal bแปi lแปnh trฦฐแปc `>` khรดng ฤฦฐแปฃc in ra mร thay vร o ฤรณ ฤฦฐแปฃc viแบฟt vร o file แป phรญa bรชn phแบฃi cแปงa `>`
+* `.gitignore`: tรชn cแปงa file mร vฤn bแบฃn sแบฝ ฤฦฐแปฃc viแบฟt vร o
+
+Vร `*` vแปi Git cรณ nghฤฉa lร "mแปi thแปฉ". Vรฌ vแบญy, nรณ sแบฝ bแป qua mแปi thแปฉ trong thฦฐ mแปฅc `.venv`.
+
+Lแปnh nร y sแบฝ tแบกo mแปt file `.gitignore` vแปi nแปi dung:
+
+```gitignore
+*
+```
+
+///
+
+## Cร i ฤแบทt gรณi (packages)
+
+Sau khi kรญch hoแบกt mรดi trฦฐแปng, bแบกn cรณ thแป cร i ฤแบทt cรกc gรณi trong ฤรณ.
+
+/// tip
+
+Thแปฑc hiแปn ฤiแปu nร y **mแปt lแบงn** khi cร i ฤแบทt hoแบทc cแบญp nhแบญt gรณi cแบงn thiแบฟt cho dแปฑ รกn cแปงa bแบกn.
+
+Nแบฟu bแบกn cแบงn cแบญp nhแบญt phiรชn bแบฃn hoแบทc thรชm mแปt gรณi mแปi, bแบกn sแบฝ **thแปฑc hiแปn ฤiแปu nร y lแบกi**.
+
+///
+
+### Cร i ฤแบทt gรณi trแปฑc tiแบฟp
+
+Nแบฟu bแบกn cแบงn cแบญp nhแบญt phiรชn bแบฃn hoแบทc thรชm mแปt gรณi mแปi, bแบกn sแบฝ **thแปฑc hiแปn ฤiแปu nร y lแบกi**.
+
+/// tip
+ฤแป quแบฃn lรฝ dแปฑ รกn tแปt hฦกn, hรฃy liแปt kรช tแบฅt cแบฃ cรกc gรณi vร phiรชn bแบฃn cแบงn thiแบฟt trong mแปt file (vรญ dแปฅ `requirements.txt` hoแบทc `pyproject.toml`).
+
+///
+
+//// tab | `pip`
+
+
+
+```console
+$ pip install "fastapi[standard]"
+
+---> 100%
+```
+
+
+
+////
+
+//// tab | `uv`
+
+Nแบฟu bแบกn cรณ
`uv`:
+
+
+
+```console
+$ uv pip install "fastapi[standard]"
+---> 100%
+```
+
+
+
+////
+
+### Cร i ฤแบทt tแปซ `requirements.txt`
+
+Nแบฟu bแบกn cรณ mแปt tแปp `requirements.txt`, bแบกn cรณ thแป sแปญ dแปฅng nรณ ฤแป cร i ฤแบทt cรกc gรณi.
+
+//// tab | `pip`
+
+
+
+```console
+$ pip install -r requirements.txt
+---> 100%
+```
+
+
+
+////
+
+//// tab | `uv`
+
+Nแบฟu bแบกn cรณ
`uv`:
+
+
+
+```console
+$ uv pip install -r requirements.txt
+---> 100%
+```
+
+
+
+////
+
+/// details | `requirements.txt`
+
+Mแปt tแปp `requirements.txt` vแปi mแปt sแป gรณi sแบฝ trรดng nhฦฐ thแบฟ nร y:
+
+```requirements.txt
+fastapi[standard]==0.113.0
+pydantic==2.8.0
+```
+
+///
+
+## Chแบกy Chฦฐฦกng trรฌnh cแปงa bแบกn
+
+Sau khi kรญch hoแบกt mรดi trฦฐแปng แบฃo, bแบกn cรณ thแป chแบกy chฦฐฦกng trรฌnh cแปงa mรฌnh, nรณ sแบฝ sแปญ dแปฅng Python trong mรดi trฦฐแปng แบฃo cแปงa bแบกn vแปi cรกc gรณi bแบกn ฤรฃ cร i ฤแบทt.
+
+
+
+```console
+$ python main.py
+
+Hello World
+```
+
+
+
+## Cแบฅu hรฌnh Trรฌnh soแบกn thแบฃo cแปงa bแบกn
+
+Nแบฟu bแบกn sแปญ dแปฅng mแปt trรฌnh soแบกn thแบฃo, hรฃy ฤแบฃm bแบฃo bแบกn cแบฅu hรฌnh nรณ ฤแป sแปญ dแปฅng cรนng mรดi trฦฐแปng แบฃo mร bแบกn ฤรฃ tแบกo (trรฌnh soแบกn thแบฃo sแบฝ tแปฑ ฤแปng phรกt hiแปn mรดi trฦฐแปng แบฃo) ฤแป bแบกn cรณ thแป nhแบญn ฤฦฐแปฃc tรญnh nฤng tแปฑ ฤแปng hoร n thร nh cรขu lแปnh (autocomplete) vร in lแปi trแปฑc tiแบฟp trong trรฌnh soแบกn thแบฃo (inline errors).
+
+Vรญ dแปฅ:
+
+*
VS Code
+*
PyCharm
+
+/// tip
+
+Bแบกn thฦฐแปng chแป cแบงn lร m ฤiแปu nร y **mแปt lแบงn**, khi bแบกn tแบกo mรดi trฦฐแปng แบฃo.
+
+///
+
+## Huแปท kรญch hoแบกt Mรดi trฦฐแปng แบฃo
+
+Khi bแบกn hoร n tแบฅt viแปc lร m trรชn dแปฑ รกn cแปงa bแบกn, bแบกn cรณ thแป **huแปท kรญch hoแบกt** mรดi trฦฐแปng แบฃo.
+
+
+
+```console
+$ deactivate
+```
+
+
+
+Nhฦฐ vแบญy, khi bแบกn chแบกy `python`, nรณ sแบฝ khรดng chแบกy tแปซ mรดi trฦฐแปng แบฃo ฤรณ vแปi cรกc gรณi ฤรฃ cร i ฤแบทt.
+
+## Sแบตn sร ng ฤแป Lร m viแปc
+
+Bรขy giแป bแบกn ฤรฃ sแบตn sร ng ฤแป lร m viแปc trรชn dแปฑ รกn cแปงa mรฌnh rแปi ฤแบฅy.
+
+/// tip
+
+Bแบกn muแปn hiแปu tแบฅt cแบฃ nhแปฏng gรฌ แป trรชn?
+
+Tiแบฟp tแปฅc ฤแปc. ๐๐ค
+
+///
+
+## Tแบกi sao cแบงn Mรดi trฦฐแปng แบฃo
+
+ฤแป lร m viแปc vแปi FastAPI, bแบกn cแบงn cร i ฤแบทt
Python.
+
+Sau ฤรณ, bแบกn sแบฝ cแบงn **cร i ฤแบทt** FastAPI vร bแบฅt kแปณ **gรณi** nร o mร bแบกn muแปn sแปญ dแปฅng.
+
+ฤแป cร i ฤแบทt gรณi, bแบกn thฦฐแปng sแปญ dแปฅng lแปnh `pip` cรณ sแบตn vแปi Python (hoแบทc cรกc phiรชn bแบฃn tฦฐฦกng tแปฑ).
+
+Tuy nhiรชn, nแบฟu bแบกn sแปญ dแปฅng `pip` trแปฑc tiแบฟp, cรกc gรณi sแบฝ ฤฦฐแปฃc cร i ฤแบทt trong **mรดi trฦฐแปng Python toร n cแปฅc** cแปงa bแบกn (phแบงn cร i ฤแบทt toร n cแปฅc cแปงa Python).
+
+### Vแบฅn ฤแป
+
+Vแบญy, vแบฅn ฤแป gรฌ khi cร i ฤแบทt gรณi trong mรดi trฦฐแปng Python toร n cแปฅc?
+
+Trong mแปt vร i thแปi ฤiแปm, bแบกn sแบฝ phแบฃi viแบฟt nhiแปu chฦฐฦกng trรฌnh khรกc nhau phแปฅ thuแปc vร o **cรกc gรณi khรกc nhau**. Vร mแปt sแป dแปฑ รกn bแบกn thแปฑc hiแปn lแบกi phแปฅ thuแปc vร o **cรกc phiรชn bแบฃn khรกc nhau** cแปงa cรนng mแปt gรณi. ๐ฑ
+
+Vรญ dแปฅ, bแบกn cรณ thแป tแบกo mแปt dแปฑ รกn ฤฦฐแปฃc gแปi lร `philosophers-stone`, chฦฐฦกng trรฌnh nร y phแปฅ thuแปc vร o mแปt gรณi khรกc ฤฦฐแปฃc gแปi lร **`harry`, sแปญ dแปฅng phiรชn bแบฃn `1`**. Vรฌ vแบญy, bแบกn cแบงn cร i ฤแบทt `harry`.
+
+```mermaid
+flowchart LR
+ stone(philosophers-stone) -->|phแปฅ thuแปc| harry-1[harry v1]
+```
+
+Sau ฤรณ, vร o mแปt vร i thแปi ฤiแปm sau, bแบกn tแบกo mแปt dแปฑ รกn khรกc ฤฦฐแปฃc gแปi lร `prisoner-of-azkaban`, vร dแปฑ รกn nร y cลฉng phแปฅ thuแปc vร o `harry`, nhฦฐng dแปฑ รกn nร y cแบงn **`harry` phiรชn bแบฃn `3`**.
+
+```mermaid
+flowchart LR
+ azkaban(prisoner-of-azkaban) --> |phแปฅ thuแปc| harry-3[harry v3]
+```
+
+Bรขy giแป, vแบฅn ฤแป lร , nแบฟu bแบกn cร i ฤแบทt cรกc gรณi toร n cแปฅc (trong mรดi trฦฐแปng toร n cแปฅc) thay vรฌ trong mแปt **mรดi trฦฐแปng แบฃo cแปฅc bแป**, bแบกn sแบฝ phแบฃi chแปn phiรชn bแบฃn `harry` nร o ฤแป cร i ฤแบทt.
+
+Nแบฟu bแบกn muแปn chแบกy `philosophers-stone` bแบกn sแบฝ cแบงn phแบฃi cร i ฤแบทt `harry` phiรชn bแบฃn `1`, vรญ dแปฅ vแปi:
+
+
+
+```console
+$ pip install "harry==1"
+```
+
+
+
+Vร sau ฤรณ bแบกn sแบฝ cรณ `harry` phiรชn bแบฃn `1` ฤฦฐแปฃc cร i ฤแบทt trong mรดi trฦฐแปng Python toร n cแปฅc cแปงa bแบกn.
+
+```mermaid
+flowchart LR
+ subgraph global[mรดi trฦฐแปng toร n cแปฅc]
+ harry-1[harry v1]
+ end
+ subgraph stone-project[dแปฑ รกn philosophers-stone ]
+ stone(philosophers-stone) -->|phแปฅ thuแปc| harry-1
+ end
+```
+
+Nhฦฐng sau ฤรณ, nแบฟu bแบกn muแปn chแบกy `prisoner-of-azkaban`, bแบกn sแบฝ cแบงn phแบฃi gแปก bแป `harry` phiรชn bแบฃn `1` vร cร i ฤแบทt `harry` phiรชn bแบฃn `3` (hoแบทc chแป cแบงn cร i ฤแบทt phiรชn bแบฃn `3` sแบฝ tแปฑ ฤแปng gแปก bแป phiรชn bแบฃn `1`).
+
+
+
+```console
+$ pip install "harry==3"
+```
+
+
+
+Vร sau ฤรณ bแบกn sแบฝ cรณ `harry` phiรชn bแบฃn `3` ฤฦฐแปฃc cร i ฤแบทt trong mรดi trฦฐแปng Python toร n cแปฅc cแปงa bแบกn.
+
+Vร nแบฟu bแบกn cแป gแบฏng chแบกy `philosophers-stone` lแบกi, cรณ khแบฃ nฤng nรณ sแบฝ **khรดng hoแบกt ฤแปng** vรฌ nรณ cแบงn `harry` phiรชn bแบฃn `1`.
+
+```mermaid
+flowchart LR
+ subgraph global[mรดi trฦฐแปng toร n cแปฅc]
+ harry-1[
harry v1]
+ style harry-1 fill:#ccc,stroke-dasharray: 5 5
+ harry-3[harry v3]
+ end
+ subgraph stone-project[dแปฑ รกn philosophers-stone ]
+ stone(philosophers-stone) -.-x|โ๏ธ| harry-1
+ end
+ subgraph azkaban-project[dแปฑ รกn prisoner-of-azkaban ]
+ azkaban(prisoner-of-azkaban) --> |phแปฅ thuแปc| harry-3
+ end
+```
+
+/// tip
+
+Mแบทc dรน cรกc gรณi Python thฦฐแปng cแป gแบฏng **trรกnh cรกc thay ฤแปi lร m hแปng code** trong **phiรชn bแบฃn mแปi**, nhฦฐng ฤแป ฤแบฃm bแบฃo an toร n, bแบกn nรชn chแปง ฤแปng cร i ฤแบทt phiรชn bแบฃn mแปi vร chแบกy kiแปm thแปญ ฤแป xรกc nhแบญn mแปi thแปฉ vแบซn hoแบกt ฤแปng ฤรบng.
+
+///
+
+Bรขy giแป, hรฃy hรฌnh dung vแป **nhiแปu** gรณi khรกc nhau mร tแบฅt cแบฃ cรกc dแปฑ รกn cแปงa bแบกn phแปฅ thuแปc vร o. Rรต rร ng rแบฅt khรณ ฤแป quแบฃn lรฝ. ฤiแปu nร y dแบซn tแปi viแปc lร bแบกn sแบฝ cรณ nhiแปu dแปฑ รกn vแปi **cรกc phiรชn bแบฃn khรดng tฦฐฦกng thรญch** cแปงa cรกc gรณi, vร bแบกn cรณ thแป khรดng biแบฟt tแบกi sao mแปt sแป thแปฉ khรดng hoแบกt ฤแปng.
+
+Hฦกn nแปฏa, tuแปณ vร o hแป ฤiแปu hร nh cแปงa bแบกn (vd Linux, Windows, macOS), cรณ thแป ฤรฃ cรณ Python ฤฦฐแปฃc cร i ฤแบทt sแบตn. Trong trฦฐแปng hแปฃp แบฅy, mแปt vร i gรณi nhiแปu khแบฃ nฤng ฤรฃ ฤฦฐแปฃc cร i ฤแบทt trฦฐแปc vแปi cรกc phiรชn bแบฃn **cแบงn thiแบฟt cho hแป thแปng cแปงa bแบกn**. Nแบฟu bแบกn cร i ฤแบทt cรกc gรณi trong mรดi trฦฐแปng Python toร n cแปฅc, bแบกn cรณ thแป sแบฝ **phรก vแปก** mแปt sแป chฦฐฦกng trรฌnh ฤรฃ ฤฦฐแปฃc cร i ฤแบทt sแบตn cรนng hแป thแปng.
+
+## Nฦกi cรกc Gรณi ฤฦฐแปฃc Cร i ฤแบทt
+
+Khi bแบกn cร i ฤแบทt Python, nรณ sแบฝ tแบกo ra mแปt vร i thฦฐ mแปฅc vร tแปp trong mรกy tรญnh cแปงa bแบกn.
+
+Mแปt vร i thฦฐ mแปฅc nร y lร nhแปฏng thฦฐ mแปฅc chแปu trรกch nhiแปm cรณ tแบฅt cแบฃ cรกc gรณi bแบกn cร i ฤแบทt.
+
+Khi bแบกn chแบกy:
+
+
+
+```console
+// ฤแปซng chแบกy lแปnh nร y ngay, ฤรขy chแป lร mแปt vรญ dแปฅ ๐ค
+$ pip install "fastapi[standard]"
+---> 100%
+```
+
+
+
+Lแปnh nร y sแบฝ tแบฃi xuแปng mแปt tแปp nรฉn vแปi mรฃ nguแปn FastAPI, thฦฐแปng lร tแปซ
PyPI.
+
+Nรณ cลฉng sแบฝ **tแบฃi xuแปng** cรกc tแปp cho cรกc gรณi khรกc mร FastAPI phแปฅ thuแปc vร o.
+
+Sau ฤรณ, nรณ sแบฝ **giแบฃi nรฉn** tแบฅt cแบฃ cรกc tแปp ฤรณ vร ฤฦฐa chรบng vร o mแปt thฦฐ mแปฅc trong mรกy tรญnh cแปงa bแบกn.
+
+Mแบทc ฤแปnh, nรณ sแบฝ ฤฦฐa cรกc tแปp ฤรฃ tแบฃi xuแปng vร giแบฃi nรฉn vร o thฦฐ mแปฅc ฤฦฐแปฃc cร i ฤแบทt cรนng Python cแปงa bแบกn, ฤรณ lร **mรดi trฦฐแปng toร n cแปฅc**.
+
+## Nhแปฏng Mรดi trฦฐแปng แบฃo lร gรฌ?
+
+Cรกch giแบฃi quyแบฟt cho vแบฅn ฤแป cรณ tแบฅt cแบฃ cรกc gรณi trong mรดi trฦฐแปng toร n cแปฅc lร sแปญ dแปฅng mแปt **mรดi trฦฐแปng แบฃo cho mแปi dแปฑ รกn** bแบกn lร m viแปc.
+
+Mแปt mรดi trฦฐแปng แบฃo lร mแปt **thฦฐ mแปฅc**, rแบฅt giแปng vแปi mรดi trฦฐแปng toร n cแปฅc, trong ฤรณ bแบกn cรณ thแป cร i ฤแบทt cรกc gรณi cho mแปt dแปฑ รกn.
+
+Vรฌ vแบญy, mแปi dแปฑ รกn sแบฝ cรณ mแปt mรดi trฦฐแปng แบฃo riรชng cแปงa nรณ (thฦฐ mแปฅc `.venv`) vแปi cรกc gรณi riรชng cแปงa nรณ.
+
+```mermaid
+flowchart TB
+ subgraph stone-project[dแปฑ รกn philosophers-stone ]
+ stone(philosophers-stone) --->|phแปฅ thuแปc| harry-1
+ subgraph venv1[.venv]
+ harry-1[harry v1]
+ end
+ end
+ subgraph azkaban-project[dแปฑ รกn prisoner-of-azkaban ]
+ azkaban(prisoner-of-azkaban) --->|phแปฅ thuแปc| harry-3
+ subgraph venv2[.venv]
+ harry-3[harry v3]
+ end
+ end
+ stone-project ~~~ azkaban-project
+```
+
+## Kรญch hoแบกt Mรดi trฦฐแปng แบฃo nghฤฉa lร gรฌ
+
+Khi bแบกn kรญch hoแบกt mแปt mรดi trฦฐแปng แบฃo, vรญ dแปฅ vแปi:
+
+//// tab | Linux, macOS
+
+
+
+```console
+$ source .venv/bin/activate
+```
+
+
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+```console
+$ .venv\Scripts\Activate.ps1
+```
+
+
+
+////
+
+//// tab | Windows Bash
+
+Nแบฟu bแบกn sแปญ dแปฅng Bash cho Windows (vรญ dแปฅ
Git Bash):
+
+
+
+```console
+$ source .venv/Scripts/activate
+```
+
+
+
+////
+
+Lแปnh nร y sแบฝ tแบกo hoแบทc sแปญa ฤแปi mแปt sแป [biแบฟn mรดi trฦฐแปng](environment-variables.md){.internal-link target=_blank} mร sแบฝ ฤฦฐแปฃc sแปญ dแปฅng cho cรกc lแปnh tiแบฟp theo.
+
+Mแปt trong sแป ฤรณ lร biแบฟn `PATH`.
+
+/// tip
+
+Bแบกn cรณ thแป tรฌm hiแปu thรชm vแป biแบฟn `PATH` trong [Biแบฟn mรดi trฦฐแปng](environment-variables.md#path-environment-variable){.internal-link target=_blank} section.
+
+///
+
+Kรญch hoแบกt mรดi trฦฐแปng แบฃo thรชm ฤฦฐแปng dแบซn `.venv/bin` (trรชn Linux vร macOS) hoแบทc `.venv\Scripts` (trรชn Windows) vร o biแบฟn `PATH`.
+
+Giแบฃ sแปญ rแบฑng trฦฐแปc khi kรญch hoแบกt mรดi trฦฐแปng, biแบฟn `PATH` nhฦฐ sau:
+
+//// tab | Linux, macOS
+
+```plaintext
+/usr/bin:/bin:/usr/sbin:/sbin
+```
+
+Nghฤฉa lร hแป thแปng sแบฝ tรฌm kiแบฟm chฦฐฦกng trรฌnh trong:
+
+* `/usr/bin`
+* `/bin`
+* `/usr/sbin`
+* `/sbin`
+
+////
+
+//// tab | Windows
+
+```plaintext
+C:\Windows\System32
+```
+
+Nghฤฉa lร hแป thแปng sแบฝ tรฌm kiแบฟm chฦฐฦกng trรฌnh trong:
+
+* `C:\Windows\System32`
+
+////
+
+Sau khi kรญch hoแบกt mรดi trฦฐแปng แบฃo, biแบฟn `PATH` sแบฝ nhฦฐ sau:
+
+//// tab | Linux, macOS
+
+```plaintext
+/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
+```
+
+Nghฤฉa lร hแป thแปng sแบฝ bแบฏt ฤแบงu tรฌm kiแบฟm chฦฐฦกng trรฌnh trong:
+
+```plaintext
+/home/user/code/awesome-project/.venv/bin
+```
+
+trฦฐแปc khi tรฌm kiแบฟm trong cรกc thฦฐ mแปฅc khรกc.
+
+Vรฌ vแบญy, khi bแบกn gรต `python` trong terminal, hแป thแปng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong:
+
+```plaintext
+/home/user/code/awesome-project/.venv/bin/python
+```
+
+vร sแปญ dแปฅng chฦฐฦกng trรฌnh ฤรณ.
+
+////
+
+//// tab | Windows
+
+```plaintext
+C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
+```
+
+Nghฤฉa lร hแป thแปng sแบฝ bแบฏt ฤแบงu tรฌm kiแบฟm chฦฐฦกng trรฌnh trong:
+
+```plaintext
+C:\Users\user\code\awesome-project\.venv\Scripts
+```
+
+trฦฐแปc khi tรฌm kiแบฟm trong cรกc thฦฐ mแปฅc khรกc.
+
+Vรฌ vแบญy, khi bแบกn gรต `python` trong terminal, hแป thแปng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong:
+
+```plaintext
+C:\Users\user\code\awesome-project\.venv\Scripts\python
+```
+
+vร sแปญ dแปฅng chฦฐฦกng trรฌnh ฤรณ.
+
+////
+
+Mแปt chi tiแบฟt quan trแปng lร nรณ sแบฝ ฤฦฐa ฤแปa chแป cแปงa mรดi trฦฐแปng แบฃo vร o **ฤแบงu** cแปงa biแบฟn `PATH`. Hแป thแปng sแบฝ tรฌm kiแบฟm nรณ **trฦฐแปc** khi tรฌm kiแบฟm bแบฅt kแปณ Python nร o khรกc cรณ sแบตn. Vรฌ vแบญy, khi bแบกn chแบกy `python`, nรณ sแบฝ sแปญ dแปฅng Python **tแปซ mรดi trฦฐแปng แบฃo** thay vรฌ bแบฅt kแปณ Python nร o khรกc (vรญ dแปฅ, Python tแปซ mรดi trฦฐแปng toร n cแปฅc).
+
+Kรญch hoแบกt mแปt mรดi trฦฐแปng แบฃo cลฉng thay ฤแปi mแปt vร i thแปฉ khรกc, nhฦฐng ฤรขy lร mแปt trong nhแปฏng ฤiแปu quan trแปng nhแบฅt mร nรณ thแปฑc hiแปn.
+
+## Kiแปm tra mแปt Mรดi trฦฐแปng แบฃo
+
+Khi bแบกn kiแปm tra mแปt mรดi trฦฐแปng แบฃo ฤรฃ ฤฦฐแปฃc kรญch hoแบกt chฦฐa, vรญ dแปฅ vแปi:
+
+//// tab | Linux, macOS, Windows Bash
+
+
+
+```console
+$ which python
+
+/home/user/code/awesome-project/.venv/bin/python
+```
+
+
+
+////
+
+//// tab | Windows PowerShell
+
+
+
+```console
+$ Get-Command python
+
+C:\Users\user\code\awesome-project\.venv\Scripts\python
+```
+
+
+
+////
+
+
+ฤiแปu ฤรณ cรณ nghฤฉa lร chฦฐฦกng trรฌnh `python` sแบฝ ฤฦฐแปฃc sแปญ dแปฅng lร chฦฐฦกng trรฌnh **trong mรดi trฦฐแปng แบฃo**.
+
+Bแบกn sแปญ dแปฅng `which` trรชn Linux vร macOS vร `Get-Command` trรชn Windows PowerShell.
+
+Cรกch hoแบกt ฤแปng cแปงa lแปnh nร y lร nรณ sแบฝ ฤi vร kiแปm tra biแบฟn `PATH`, ฤi qua **mแปi ฤฦฐแปng dแบซn theo thแปฉ tแปฑ**, tรฌm kiแบฟm chฦฐฦกng trรฌnh ฤฦฐแปฃc gแปi lร `python`. Khi nรณ tรฌm thแบฅy nรณ, nรณ sแบฝ **hiแปn thแป cho bแบกn ฤฦฐแปng dแบซn** ฤแบฟn chฦฐฦกng trรฌnh ฤรณ.
+
+ฤiแปu quan trแปng nhแบฅt lร khi bแบกn gแปi `python`, ฤรณ chรญnh lร chฦฐฦกng trรฌnh `python` ฤฦฐแปฃc thแปฑc thi.
+
+Vรฌ vแบญy, bแบกn cรณ thแป xรกc nhแบญn nแบฟu bแบกn ฤang แป trong mรดi trฦฐแปng แบฃo ฤรบng.
+
+/// tip
+
+Dแป
dร ng kรญch hoแบกt mแปt mรดi trฦฐแปng แบฃo, cร i ฤแบทt Python, vร sau ฤรณ **chuyแปn ฤแบฟn mแปt dแปฑ รกn khรกc**.
+
+Vร dแปฑ รกn thแปฉ hai **sแบฝ khรดng hoแบกt ฤแปng** vรฌ bแบกn ฤang sแปญ dแปฅng **Python khรดng ฤรบng**, tแปซ mแปt mรดi trฦฐแปng แบฃo cho mแปt dแปฑ รกn khรกc.
+
+Thแบญt tiแปn lแปฃi khi cรณ thแป kiแปm tra `python` nร o ฤang ฤฦฐแปฃc sแปญ dแปฅng ๐ค
+
+///
+
+## Tแบกi sao lแบกi Huแปท kรญch hoแบกt mแปt Mรดi trฦฐแปng แบฃo
+
+Vรญ dแปฅ, bแบกn cรณ thแป lร m viแปc trรชn mแปt dแปฑ รกn `philosophers-stone`, **kรญch hoแบกt mรดi trฦฐแปng แบฃo**, cร i ฤแบทt cรกc gรณi vร lร m viแปc vแปi mรดi trฦฐแปng แบฃo ฤรณ.
+
+Sau ฤรณ, bแบกn muแปn lร m viแปc trรชn **dแปฑ รกn khรกc** `prisoner-of-azkaban`.
+
+Bแบกn ฤi ฤแบฟn dแปฑ รกn ฤรณ:
+
+
+
+```console
+$ cd ~/code/prisoner-of-azkaban
+```
+
+
+
+Nแบฟu bแบกn khรดng tแบฏt mรดi trฦฐแปng แบฃo cho `philosophers-stone`, khi bแบกn chแบกy `python` trong terminal, nรณ sแบฝ cแป gแบฏng sแปญ dแปฅng Python tแปซ `philosophers-stone`.
+
+
+
+```console
+$ cd ~/code/prisoner-of-azkaban
+
+$ python main.py
+
+// Lแปi khi import sirius, nรณ khรดng ฤฦฐแปฃc cร i ฤแบทt ๐ฑ
+Traceback (most recent call last):
+ File "main.py", line 1, in
+ import sirius
+```
+
+
+
+Nแบฟu bแบกn huแปท kรญch hoแบกt mรดi trฦฐแปng แบฃo hiแปn tแบกi vร kรญch hoแบกt mรดi trฦฐแปng แบฃo mแปi cho `prisoner-of-azkaban`, khi bแบกn chแบกy `python`, nรณ sแบฝ sแปญ dแปฅng Python tแปซ mรดi trฦฐแปng แบฃo trong `prisoner-of-azkaban`.
+
+
+
+```console
+$ cd ~/code/prisoner-of-azkaban
+
+// Bแบกn khรดng cแบงn phแบฃi แป trong thฦฐ mแปฅc trฦฐแปc ฤแป huแปท kรญch hoแบกt, bแบกn cรณ thแป lร m ฤiแปu ฤรณ แป bแบฅt kแปณ ฤรขu, ngay cแบฃ sau khi ฤi ฤแบฟn dแปฑ รกn khรกc ๐
+$ deactivate
+
+// Kรญch hoแบกt mรดi trฦฐแปng แบฃo trong prisoner-of-azkaban/.venv ๐
+$ source .venv/bin/activate
+
+// Bรขy giแป khi bแบกn chแบกy python, nรณ sแบฝ tรฌm thแบฅy gรณi sirius ฤฦฐแปฃc cร i ฤแบทt trong mรดi trฦฐแปng แบฃo nร y โจ
+$ python main.py
+
+I solemnly swear ๐บ
+
+(Tรดi long trแปng thแป ๐บ - cรขu nร y ฤฦฐแปฃc lแบฅy tแปซ Harry Potter, chรบ thรญch cแปงa ngฦฐแปi dแปch)
+```
+
+
+
+## Cรกc cรกch lร m tฦฐฦกng tแปฑ
+
+ฤรขy lร mแปt hฦฐแปng dแบซn ฤฦกn giแบฃn ฤแป bแบกn cรณ thแป bแบฏt ฤแบงu vร hiแปu cรกch mแปi thแปฉ hoแบกt ฤแปng **bรชn trong**.
+
+Cรณ nhiแปu **cรกch khรกc nhau** ฤแป quแบฃn lรญ cรกc mรดi trฦฐแปng แบฃo, cรกc gรณi phแปฅ thuแปc (requirements), vร cรกc dแปฑ รกn.
+
+Mแปt khi bแบกn ฤรฃ sแบตn sร ng vร muแปn sแปญ dแปฅng mแปt cรดng cแปฅ ฤแป **quแบฃn lรญ cแบฃ dแปฑ รกn**, cรกc gรณi phแปฅ thuแปc, cรกc mรดi trฦฐแปng แบฃo, v.v. Tรดi sแบฝ khuyรชn bแบกn nรชn thแปญ
uv.
+
+`uv` cรณ thแป lร m nhiแปu thแปฉ, chแบณng hแบกn:
+
+* **Cร i ฤแบทt Python** cho bแบกn, bao gแปm nhiแปu phiรชn bแบฃn khรกc nhau
+* Quแบฃn lรญ **cรกc mรดi trฦฐแปng แบฃo** cho cรกc dแปฑ รกn cแปงa bแบกn
+* Cร i ฤแบทt **cรกc gรณi (packages)**
+* Quแบฃn lรญ **cรกc thร nh phแบงn phแปฅ thuแปc vร phiรชn bแบฃn** cแปงa cรกc gรณi cho dแปฑ รกn cแปงa bแบกn
+* ฤแบฃm bแบฃo rแบฑng bแบกn cรณ mแปt **tแบญp hแปฃp chรญnh xรกc** cรกc gรณi vร phiรชn bแบฃn ฤแป cร i ฤแบทt, bao gแปm cรกc thร nh phแบงn phแปฅ thuแปc cแปงa chรบng, ฤแป bแบกn cรณ thแป ฤแบฃm bแบฃo rแบฑng bแบกn cรณ thแป chแบกy dแปฑ รกn cแปงa bแบกn trong sแบฃn xuแบฅt chรญnh xรกc nhฦฐ trong mรกy tรญnh cแปงa bแบกn trong khi phรกt triแปn, ฤiแปu nร y ฤฦฐแปฃc gแปi lร **locking**
+* Vร cรฒn nhiแปu thแปฉ khรกc nแปฏa
+
+## Kแบฟt luแบญn
+
+Nแบฟu bแบกn ฤรฃ ฤแปc vร hiแปu hแบฟt nhแปฏng ฤiแปu nร y, khรก chแบฏc lร bรขy giแป bแบกn ฤรฃ **biแบฟt nhiแปu hฦกn** vแป mรดi trฦฐแปng แบฃo so vแปi kha khรก lแบญp trรฌnh viรชn khรกc ฤแบฅy. ๐ค
+
+Nhแปฏng hiแปu biแบฟt chi tiแบฟt nร y cรณ thแป sแบฝ hแปฏu รญch vแปi bแบกn trong tฦฐฦกng lai khi mร bแบกn cแบงn gแปก lแปi mแปt vร i thแปฉ phแปฉc tแบกp, vร bแบกn ฤรฃ cรณ nhแปฏng hiแปu biแบฟt vแป **ngแปn ngร nh gแปc rแป
cรกch nรณ hoแบกt ฤแปng**. ๐
diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md
index 2fba671f7..c2f9a7e9f 100644
--- a/docs/zh/docs/tutorial/query-params-str-validations.md
+++ b/docs/zh/docs/tutorial/query-params-str-validations.md
@@ -108,21 +108,6 @@ q: Union[str, None] = Query(default=None, min_length=3)
{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *}
-### ไฝฟ็จ็็ฅๅท(`...`)ๅฃฐๆๅฟ
้ๅๆฐ
-
-ๆๅฆไธ็งๆนๆณๅฏไปฅๆพๅผ็ๅฃฐๆไธไธชๅผๆฏๅฟ
้็๏ผๅณๅฐ้ป่ฎคๅๆฐ็้ป่ฎคๅผ่ฎพไธบ `...` ๏ผ
-
-{* ../../docs_src/query_params_str_validations/tutorial006b.py hl[7] *}
-
-/// info
-
-ๅฆๆไฝ ไนๅๆฒก่ง่ฟ `...` ่ฟ็ง็จๆณ๏ผๅฎๆฏไธไธช็นๆฎ็ๅ็ฌๅผ๏ผๅฎๆฏ
Python ็ไธ้จๅๅนถไธ่ขซ็งฐไธบโEllipsisโ๏ผๆไธบ็็ฅๅท โโ ่ฏ่
ๆณจ๏ผใ
-Pydantic ๅ FastAPI ไฝฟ็จๅฎๆฅๆพๅผ็ๅฃฐๆ้่ฆไธไธชๅผใ
-
-///
-
-่ฟๅฐไฝฟ **FastAPI** ็ฅ้ๆญคๆฅ่ฏขๅๆฐๆฏๅฟ
้็ใ
-
### ไฝฟ็จ`None`ๅฃฐๆๅฟ
้ๅๆฐ
ไฝ ๅฏไปฅๅฃฐๆไธไธชๅๆฐๅฏไปฅๆฅๆถ`None`ๅผ๏ผไฝๅฎไป็ถๆฏๅฟ
้็ใ่ฟๅฐๅผบๅถๅฎขๆท็ซฏๅ้ไธไธชๅผ๏ผๅณไฝฟ่ฏฅๅผๆฏ`None`ใ
@@ -137,18 +122,6 @@ Pydantic ๆฏ FastAPI ไธญๆๆๆฐๆฎ้ช่ฏๅๅบๅๅ็ๆ ธๅฟ๏ผๅฝไฝ ๅจๆฒก
///
-### ไฝฟ็จPydanticไธญ็`Required`ไปฃๆฟ็็ฅๅท(`...`)
-
-ๅฆๆไฝ ่งๅพไฝฟ็จ `...` ไธ่ๆ๏ผไฝ ไนๅฏไปฅไป Pydantic ๅฏผๅ
ฅๅนถไฝฟ็จ `Required`๏ผ
-
-{* ../../docs_src/query_params_str_validations/tutorial006d.py hl[2,8] *}
-
-/// tip
-
-่ฏท่ฎฐไฝ๏ผๅจๅคงๅคๆฐๆ
ๅตไธ๏ผๅฝไฝ ้่ฆๆไบไธ่ฅฟๆถ๏ผๅฏไปฅ็ฎๅๅฐ็็ฅ `default` ๅๆฐ๏ผๅ ๆญคไฝ ้ๅธธไธๅฟ
ไฝฟ็จ `...` ๆ `Required`
-
-///
-
## ๆฅ่ฏขๅๆฐๅ่กจ / ๅคไธชๅผ
ๅฝไฝ ไฝฟ็จ `Query` ๆพๅผๅฐๅฎไนๆฅ่ฏขๅๆฐๆถ๏ผไฝ ่ฟๅฏไปฅๅฃฐๆๅฎๅปๆฅๆถไธ็ปๅผ๏ผๆๆขๅฅ่ฏๆฅ่ฏด๏ผๆฅๆถๅคไธชๅผใ
diff --git a/docs_src/body/tutorial002.py b/docs_src/body/tutorial002.py
index 7f5183908..5cd86216b 100644
--- a/docs_src/body/tutorial002.py
+++ b/docs_src/body/tutorial002.py
@@ -17,7 +17,7 @@ app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
- if item.tax:
+ if item.tax is not None:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
diff --git a/docs_src/body/tutorial002_py310.py b/docs_src/body/tutorial002_py310.py
index 8928b72b8..454c45c88 100644
--- a/docs_src/body/tutorial002_py310.py
+++ b/docs_src/body/tutorial002_py310.py
@@ -15,7 +15,7 @@ app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
- if item.tax:
+ if item.tax is not None:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
diff --git a/docs_src/configure_swagger_ui/tutorial002.py b/docs_src/configure_swagger_ui/tutorial002.py
index cc569ce45..cc75c2196 100644
--- a/docs_src/configure_swagger_ui/tutorial002.py
+++ b/docs_src/configure_swagger_ui/tutorial002.py
@@ -1,6 +1,6 @@
from fastapi import FastAPI
-app = FastAPI(swagger_ui_parameters={"syntaxHighlight.theme": "obsidian"})
+app = FastAPI(swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}})
@app.get("/users/{username}")
diff --git a/docs_src/query_params_str_validations/tutorial006b.py b/docs_src/query_params_str_validations/tutorial006b.py
deleted file mode 100644
index a8d69c889..000000000
--- a/docs_src/query_params_str_validations/tutorial006b.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from fastapi import FastAPI, Query
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: str = Query(default=..., min_length=3)):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial006b_an.py b/docs_src/query_params_str_validations/tutorial006b_an.py
deleted file mode 100644
index ea3b02583..000000000
--- a/docs_src/query_params_str_validations/tutorial006b_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial006b_an_py39.py b/docs_src/query_params_str_validations/tutorial006b_an_py39.py
deleted file mode 100644
index 687a9f544..000000000
--- a/docs_src/query_params_str_validations/tutorial006b_an_py39.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from typing import Annotated
-
-from fastapi import FastAPI, Query
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial006c.py b/docs_src/query_params_str_validations/tutorial006c.py
index 2ac148c94..0a0e820da 100644
--- a/docs_src/query_params_str_validations/tutorial006c.py
+++ b/docs_src/query_params_str_validations/tutorial006c.py
@@ -6,7 +6,7 @@ app = FastAPI()
@app.get("/items/")
-async def read_items(q: Union[str, None] = Query(default=..., min_length=3)):
+async def read_items(q: Union[str, None] = Query(min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py
index 10bf26a57..55c4f4adc 100644
--- a/docs_src/query_params_str_validations/tutorial006c_an.py
+++ b/docs_src/query_params_str_validations/tutorial006c_an.py
@@ -7,7 +7,7 @@ app = FastAPI()
@app.get("/items/")
-async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
+async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py310.py b/docs_src/query_params_str_validations/tutorial006c_an_py310.py
index 1ab0a7d53..2995d9c97 100644
--- a/docs_src/query_params_str_validations/tutorial006c_an_py310.py
+++ b/docs_src/query_params_str_validations/tutorial006c_an_py310.py
@@ -6,7 +6,7 @@ app = FastAPI()
@app.get("/items/")
-async def read_items(q: Annotated[str | None, Query(min_length=3)] = ...):
+async def read_items(q: Annotated[str | None, Query(min_length=3)]):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py39.py b/docs_src/query_params_str_validations/tutorial006c_an_py39.py
index ac1273331..76a1cd49a 100644
--- a/docs_src/query_params_str_validations/tutorial006c_an_py39.py
+++ b/docs_src/query_params_str_validations/tutorial006c_an_py39.py
@@ -6,7 +6,7 @@ app = FastAPI()
@app.get("/items/")
-async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
+async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
diff --git a/docs_src/query_params_str_validations/tutorial006c_py310.py b/docs_src/query_params_str_validations/tutorial006c_py310.py
index 82dd9e5d7..88b499c7a 100644
--- a/docs_src/query_params_str_validations/tutorial006c_py310.py
+++ b/docs_src/query_params_str_validations/tutorial006c_py310.py
@@ -4,7 +4,7 @@ app = FastAPI()
@app.get("/items/")
-async def read_items(q: str | None = Query(default=..., min_length=3)):
+async def read_items(q: str | None = Query(min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
diff --git a/docs_src/query_params_str_validations/tutorial006d.py b/docs_src/query_params_str_validations/tutorial006d.py
deleted file mode 100644
index a8d69c889..000000000
--- a/docs_src/query_params_str_validations/tutorial006d.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from fastapi import FastAPI, Query
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: str = Query(default=..., min_length=3)):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial006d_an.py b/docs_src/query_params_str_validations/tutorial006d_an.py
deleted file mode 100644
index ea3b02583..000000000
--- a/docs_src/query_params_str_validations/tutorial006d_an.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from fastapi import FastAPI, Query
-from typing_extensions import Annotated
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/query_params_str_validations/tutorial006d_an_py39.py b/docs_src/query_params_str_validations/tutorial006d_an_py39.py
deleted file mode 100644
index 687a9f544..000000000
--- a/docs_src/query_params_str_validations/tutorial006d_an_py39.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from typing import Annotated
-
-from fastapi import FastAPI, Query
-
-app = FastAPI()
-
-
-@app.get("/items/")
-async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
- results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
- if q:
- results.update({"q": q})
- return results
diff --git a/docs_src/security/tutorial004.py b/docs_src/security/tutorial004.py
index 91d161b8a..222589618 100644
--- a/docs_src/security/tutorial004.py
+++ b/docs_src/security/tutorial004.py
@@ -95,7 +95,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py
index df50754af..e2221cd39 100644
--- a/docs_src/security/tutorial004_an.py
+++ b/docs_src/security/tutorial004_an.py
@@ -96,7 +96,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py
index eff54ef01..a3f74fc0e 100644
--- a/docs_src/security/tutorial004_an_py310.py
+++ b/docs_src/security/tutorial004_an_py310.py
@@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py
index 0455b500c..b33d677ed 100644
--- a/docs_src/security/tutorial004_an_py39.py
+++ b/docs_src/security/tutorial004_an_py39.py
@@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py
index 78bee22a3..d46ce26bf 100644
--- a/docs_src/security/tutorial004_py310.py
+++ b/docs_src/security/tutorial004_py310.py
@@ -94,7 +94,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py
index 5b67cb145..2e8bb3bdb 100644
--- a/docs_src/security/tutorial005_an.py
+++ b/docs_src/security/tutorial005_an.py
@@ -117,7 +117,7 @@ async def get_current_user(
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_scopes = payload.get("scopes", [])
diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py
index 297193e35..90781587f 100644
--- a/docs_src/security/tutorial005_an_py310.py
+++ b/docs_src/security/tutorial005_an_py310.py
@@ -116,7 +116,7 @@ async def get_current_user(
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_scopes = payload.get("scopes", [])
diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py
index 1acf47bdc..a5192d8d6 100644
--- a/docs_src/security/tutorial005_an_py39.py
+++ b/docs_src/security/tutorial005_an_py39.py
@@ -1,5 +1,5 @@
from datetime import datetime, timedelta, timezone
-from typing import Annotated, List, Union
+from typing import Annotated, Union
import jwt
from fastapi import Depends, FastAPI, HTTPException, Security, status
@@ -44,7 +44,7 @@ class Token(BaseModel):
class TokenData(BaseModel):
username: Union[str, None] = None
- scopes: List[str] = []
+ scopes: list[str] = []
class User(BaseModel):
@@ -116,7 +116,7 @@ async def get_current_user(
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- username: str = payload.get("sub")
+ username = payload.get("sub")
if username is None:
raise credentials_exception
token_scopes = payload.get("scopes", [])
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 823957822..e3e0200ae 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.115.6"
+__version__ = "0.115.8"
from starlette import status as status
diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py
index d68bdb037..70c2dca8a 100644
--- a/fastapi/security/api_key.py
+++ b/fastapi/security/api_key.py
@@ -9,7 +9,15 @@ from typing_extensions import Annotated, Doc
class APIKeyBase(SecurityBase):
- pass
+ @staticmethod
+ def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:
+ if not api_key:
+ if auto_error:
+ raise HTTPException(
+ status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
+ )
+ return None
+ return api_key
class APIKeyQuery(APIKeyBase):
@@ -101,14 +109,7 @@ class APIKeyQuery(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.query_params.get(self.model.name)
- if not api_key:
- if self.auto_error:
- raise HTTPException(
- status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
- )
- else:
- return None
- return api_key
+ return self.check_api_key(api_key, self.auto_error)
class APIKeyHeader(APIKeyBase):
@@ -196,14 +197,7 @@ class APIKeyHeader(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.headers.get(self.model.name)
- if not api_key:
- if self.auto_error:
- raise HTTPException(
- status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
- )
- else:
- return None
- return api_key
+ return self.check_api_key(api_key, self.auto_error)
class APIKeyCookie(APIKeyBase):
@@ -291,11 +285,4 @@ class APIKeyCookie(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.cookies.get(self.model.name)
- if not api_key:
- if self.auto_error:
- raise HTTPException(
- status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
- )
- else:
- return None
- return api_key
+ return self.check_api_key(api_key, self.auto_error)
diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py
index 6adc55bfe..5ffad5986 100644
--- a/fastapi/security/oauth2.py
+++ b/fastapi/security/oauth2.py
@@ -63,7 +63,7 @@ class OAuth2PasswordRequestForm:
*,
grant_type: Annotated[
Union[str, None],
- Form(pattern="password"),
+ Form(pattern="^password$"),
Doc(
"""
The OAuth2 spec says it is required and MUST be the fixed string
@@ -217,7 +217,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
self,
grant_type: Annotated[
str,
- Form(pattern="password"),
+ Form(pattern="^password$"),
Doc(
"""
The OAuth2 spec says it is required and MUST be the fixed string
diff --git a/pyproject.toml b/pyproject.toml
index edfa81522..51d63fd44 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,6 +29,7 @@ classifiers = [
"Framework :: FastAPI",
"Framework :: Pydantic",
"Framework :: Pydantic :: 1",
+ "Framework :: Pydantic :: 2",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
@@ -37,11 +38,12 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
- "starlette>=0.40.0,<0.42.0",
+ "starlette>=0.40.0,<0.46.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.8.0",
]
@@ -60,9 +62,9 @@ standard = [
# For the test client
"httpx >=0.23.0",
# For templates
- "jinja2 >=2.11.2",
+ "jinja2 >=3.1.5",
# For forms and file uploads
- "python-multipart >=0.0.7",
+ "python-multipart >=0.0.18",
# To validate email fields
"email-validator >=2.0.0",
# Uvicorn with uvloop
@@ -79,9 +81,9 @@ all = [
# # For the test client
"httpx >=0.23.0",
# For templates
- "jinja2 >=2.11.2",
+ "jinja2 >=3.1.5",
# For forms and file uploads
- "python-multipart >=0.0.7",
+ "python-multipart >=0.0.18",
# For Starlette's SessionMiddleware, not commonly used with FastAPI
"itsdangerous >=1.1.0",
# For Starlette's schema generation, would not be used with FastAPI
@@ -161,6 +163,8 @@ filterwarnings = [
# Ref: https://github.com/python-trio/trio/pull/3054
# Remove once there's a new version of Trio
'ignore:The `hash` argument is deprecated*:DeprecationWarning:trio',
+ # Ignore flaky coverage / pytest warning about SQLite connection, only applies to Python 3.13 and Pydantic v1
+ 'ignore:Exception ignored in.
=1.4.1,<2.0.0
mkdocs-redirects>=1.2.1,<1.3.0
typer == 0.12.5
@@ -8,7 +8,7 @@ pyyaml >=5.3.1,<7.0.0
# For Material for MkDocs, Chinese search
jieba==0.42.1
# For image processing by Material for MkDocs
-pillow==11.0.0
+pillow==11.1.0
# For image processing by Material for MkDocs
cairosvg==2.7.1
mkdocstrings[python]==0.26.1
diff --git a/requirements-tests.txt b/requirements-tests.txt
index 5be052307..4a15844e4 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -6,11 +6,11 @@ mypy ==1.8.0
dirty-equals ==0.8.0
sqlmodel==0.0.22
flask >=1.1.2,<4.0.0
-anyio[trio] >=3.2.1,<4.0.0
+anyio[trio] >=3.2.1,<5.0.0
PyJWT==2.8.0
pyyaml >=5.3.1,<7.0.0
passlib[bcrypt] >=1.7.2,<2.0.0
-inline-snapshot==0.18.1
+inline-snapshot==0.19.3
# types
types-ujson ==5.10.0.20240515
types-orjson ==3.6.2
diff --git a/.github/actions/notify-translations/app/main.py b/scripts/notify_translations.py
similarity index 88%
rename from .github/actions/notify-translations/app/main.py
rename to scripts/notify_translations.py
index 716232d49..c300624db 100644
--- a/.github/actions/notify-translations/app/main.py
+++ b/scripts/notify_translations.py
@@ -7,12 +7,13 @@ from typing import Any, Dict, List, Union, cast
import httpx
from github import Github
-from pydantic import BaseModel, BaseSettings, SecretStr
+from pydantic import BaseModel, SecretStr
+from pydantic_settings import BaseSettings
awaiting_label = "awaiting-review"
lang_all_label = "lang-all"
approved_label = "approved-1"
-translations_path = Path(__file__).parent / "translations.yml"
+
github_graphql_url = "https://api.github.com/graphql"
questions_translations_category_id = "DIC_kwDOCZduT84CT5P9"
@@ -175,20 +176,23 @@ class AllDiscussionsResponse(BaseModel):
class Settings(BaseSettings):
+ model_config = {"env_ignore_empty": True}
+
github_repository: str
- input_token: SecretStr
+ github_token: SecretStr
github_event_path: Path
github_event_name: Union[str, None] = None
httpx_timeout: int = 30
- input_debug: Union[bool, None] = False
+ debug: Union[bool, None] = False
+ number: int | None = None
class PartialGitHubEventIssue(BaseModel):
- number: int
+ number: int | None = None
class PartialGitHubEvent(BaseModel):
- pull_request: PartialGitHubEventIssue
+ pull_request: PartialGitHubEventIssue | None = None
def get_graphql_response(
@@ -202,9 +206,7 @@ def get_graphql_response(
comment_id: Union[str, None] = None,
body: Union[str, None] = None,
) -> Dict[str, Any]:
- headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
- # some fields are only used by one query, but GraphQL allows unused variables, so
- # keep them here for simplicity
+ headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
variables = {
"after": after,
"category_id": category_id,
@@ -228,37 +230,40 @@ 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 cast(Dict[str, Any], data)
-def get_graphql_translation_discussions(*, settings: Settings):
+def get_graphql_translation_discussions(
+ *, settings: Settings
+) -> List[AllDiscussionsDiscussionNode]:
data = get_graphql_response(
settings=settings,
query=all_discussions_query,
category_id=questions_translations_category_id,
)
- graphql_response = AllDiscussionsResponse.parse_obj(data)
+ graphql_response = AllDiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.nodes
def get_graphql_translation_discussion_comments_edges(
*, settings: Settings, discussion_number: int, after: Union[str, None] = None
-):
+) -> List[CommentsEdge]:
data = get_graphql_response(
settings=settings,
query=translation_discussion_query,
discussion_number=discussion_number,
after=after,
)
- graphql_response = CommentsResponse.parse_obj(data)
+ graphql_response = CommentsResponse.model_validate(data)
return graphql_response.data.repository.discussion.comments.edges
def get_graphql_translation_discussion_comments(
*, settings: Settings, discussion_number: int
-):
+) -> list[Comment]:
comment_nodes: List[Comment] = []
discussion_edges = get_graphql_translation_discussion_comments_edges(
settings=settings, discussion_number=discussion_number
@@ -276,43 +281,49 @@ def get_graphql_translation_discussion_comments(
return comment_nodes
-def create_comment(*, settings: Settings, discussion_id: str, body: str):
+def create_comment(*, settings: Settings, discussion_id: str, body: str) -> Comment:
data = get_graphql_response(
settings=settings,
query=add_comment_mutation,
discussion_id=discussion_id,
body=body,
)
- response = AddCommentResponse.parse_obj(data)
+ response = AddCommentResponse.model_validate(data)
return response.data.addDiscussionComment.comment
-def update_comment(*, settings: Settings, comment_id: str, body: str):
+def update_comment(*, settings: Settings, comment_id: str, body: str) -> Comment:
data = get_graphql_response(
settings=settings,
query=update_comment_mutation,
comment_id=comment_id,
body=body,
)
- response = UpdateCommentResponse.parse_obj(data)
+ response = UpdateCommentResponse.model_validate(data)
return response.data.updateDiscussionComment.comment
-if __name__ == "__main__":
+def main() -> None:
settings = Settings()
- if settings.input_debug:
+ if settings.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
- logging.debug(f"Using config: {settings.json()}")
- g = Github(settings.input_token.get_secret_value())
+ logging.debug(f"Using config: {settings.model_dump_json()}")
+ g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
if not settings.github_event_path.is_file():
raise RuntimeError(
f"No github event file available at: {settings.github_event_path}"
)
contents = settings.github_event_path.read_text()
- github_event = PartialGitHubEvent.parse_raw(contents)
+ github_event = PartialGitHubEvent.model_validate_json(contents)
+ logging.info(f"Using GitHub event: {github_event}")
+ number = (
+ github_event.pull_request and github_event.pull_request.number
+ ) or settings.number
+ if number is None:
+ raise RuntimeError("No PR number available")
# Avoid race conditions with multiple labels
sleep_time = random.random() * 10 # random number between 0 and 10 seconds
@@ -323,8 +334,8 @@ if __name__ == "__main__":
time.sleep(sleep_time)
# Get PR
- logging.debug(f"Processing PR: #{github_event.pull_request.number}")
- pr = repo.get_pull(github_event.pull_request.number)
+ logging.debug(f"Processing PR: #{number}")
+ pr = repo.get_pull(number)
label_strs = {label.name for label in pr.get_labels()}
langs = []
for label in label_strs:
@@ -415,3 +426,7 @@ if __name__ == "__main__":
f"There doesn't seem to be anything to be done about PR #{pr.number}"
)
logging.info("Finished")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/people.py b/scripts/people.py
new file mode 100644
index 000000000..f61fd31c9
--- /dev/null
+++ b/scripts/people.py
@@ -0,0 +1,401 @@
+import logging
+import secrets
+import subprocess
+import time
+from collections import Counter
+from datetime import datetime, timedelta, timezone
+from pathlib import Path
+from typing import Any, Container, Union
+
+import httpx
+import yaml
+from github import Github
+from pydantic import BaseModel, SecretStr
+from pydantic_settings import BaseSettings
+
+github_graphql_url = "https://api.github.com/graphql"
+questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0"
+
+discussions_query = """
+query Q($after: String, $category_id: ID) {
+ repository(name: "fastapi", owner: "fastapi") {
+ discussions(first: 100, after: $after, categoryId: $category_id) {
+ edges {
+ cursor
+ node {
+ number
+ author {
+ login
+ avatarUrl
+ url
+ }
+ createdAt
+ comments(first: 50) {
+ totalCount
+ nodes {
+ createdAt
+ author {
+ login
+ avatarUrl
+ url
+ }
+ isAnswer
+ replies(first: 10) {
+ totalCount
+ nodes {
+ createdAt
+ author {
+ login
+ avatarUrl
+ url
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+"""
+
+
+class Author(BaseModel):
+ login: str
+ avatarUrl: str | None = None
+ url: str | None = None
+
+
+class CommentsNode(BaseModel):
+ createdAt: datetime
+ author: Union[Author, None] = None
+
+
+class Replies(BaseModel):
+ totalCount: int
+ nodes: list[CommentsNode]
+
+
+class DiscussionsCommentsNode(CommentsNode):
+ replies: Replies
+
+
+class DiscussionsComments(BaseModel):
+ totalCount: int
+ nodes: list[DiscussionsCommentsNode]
+
+
+class DiscussionsNode(BaseModel):
+ number: int
+ author: Union[Author, None] = None
+ title: str | None = None
+ createdAt: datetime
+ comments: DiscussionsComments
+
+
+class DiscussionsEdge(BaseModel):
+ cursor: str
+ node: DiscussionsNode
+
+
+class Discussions(BaseModel):
+ edges: list[DiscussionsEdge]
+
+
+class DiscussionsRepository(BaseModel):
+ discussions: Discussions
+
+
+class DiscussionsResponseData(BaseModel):
+ repository: DiscussionsRepository
+
+
+class DiscussionsResponse(BaseModel):
+ data: DiscussionsResponseData
+
+
+class Settings(BaseSettings):
+ github_token: SecretStr
+ github_repository: str
+ httpx_timeout: int = 30
+
+
+def get_graphql_response(
+ *,
+ settings: Settings,
+ query: str,
+ after: Union[str, None] = None,
+ category_id: Union[str, None] = None,
+) -> dict[str, Any]:
+ headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
+ variables = {"after": after, "category_id": category_id}
+ response = httpx.post(
+ github_graphql_url,
+ headers=headers,
+ timeout=settings.httpx_timeout,
+ json={"query": query, "variables": variables, "operationName": "Q"},
+ )
+ if response.status_code != 200:
+ logging.error(
+ f"Response was not 200, after: {after}, category_id: {category_id}"
+ )
+ logging.error(response.text)
+ raise RuntimeError(response.text)
+ 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
+
+
+def get_graphql_question_discussion_edges(
+ *,
+ settings: Settings,
+ after: Union[str, None] = None,
+) -> list[DiscussionsEdge]:
+ data = get_graphql_response(
+ settings=settings,
+ query=discussions_query,
+ after=after,
+ category_id=questions_category_id,
+ )
+ graphql_response = DiscussionsResponse.model_validate(data)
+ return graphql_response.data.repository.discussions.edges
+
+
+class DiscussionExpertsResults(BaseModel):
+ commenters: Counter[str]
+ last_month_commenters: Counter[str]
+ three_months_commenters: Counter[str]
+ six_months_commenters: Counter[str]
+ one_year_commenters: Counter[str]
+ authors: dict[str, Author]
+
+
+def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]:
+ discussion_nodes: list[DiscussionsNode] = []
+ discussion_edges = get_graphql_question_discussion_edges(settings=settings)
+
+ while discussion_edges:
+ for discussion_edge in discussion_edges:
+ discussion_nodes.append(discussion_edge.node)
+ last_edge = discussion_edges[-1]
+ # Handle GitHub secondary rate limits, requests per minute
+ time.sleep(5)
+ discussion_edges = get_graphql_question_discussion_edges(
+ settings=settings, after=last_edge.cursor
+ )
+ return discussion_nodes
+
+
+def get_discussions_experts(
+ discussion_nodes: list[DiscussionsNode],
+) -> DiscussionExpertsResults:
+ commenters = Counter[str]()
+ last_month_commenters = Counter[str]()
+ three_months_commenters = Counter[str]()
+ six_months_commenters = Counter[str]()
+ one_year_commenters = Counter[str]()
+ authors: dict[str, Author] = {}
+
+ now = datetime.now(tz=timezone.utc)
+ one_month_ago = now - timedelta(days=30)
+ three_months_ago = now - timedelta(days=90)
+ six_months_ago = now - timedelta(days=180)
+ one_year_ago = now - timedelta(days=365)
+
+ for discussion in discussion_nodes:
+ discussion_author_name = None
+ if discussion.author:
+ authors[discussion.author.login] = discussion.author
+ discussion_author_name = discussion.author.login
+ discussion_commentors: dict[str, datetime] = {}
+ for comment in discussion.comments.nodes:
+ if comment.author:
+ authors[comment.author.login] = comment.author
+ if comment.author.login != discussion_author_name:
+ author_time = discussion_commentors.get(
+ comment.author.login, comment.createdAt
+ )
+ discussion_commentors[comment.author.login] = max(
+ author_time, comment.createdAt
+ )
+ for reply in comment.replies.nodes:
+ if reply.author:
+ authors[reply.author.login] = reply.author
+ if reply.author.login != discussion_author_name:
+ author_time = discussion_commentors.get(
+ reply.author.login, reply.createdAt
+ )
+ discussion_commentors[reply.author.login] = max(
+ author_time, reply.createdAt
+ )
+ for author_name, author_time in discussion_commentors.items():
+ commenters[author_name] += 1
+ if author_time > one_month_ago:
+ last_month_commenters[author_name] += 1
+ if author_time > three_months_ago:
+ three_months_commenters[author_name] += 1
+ if author_time > six_months_ago:
+ six_months_commenters[author_name] += 1
+ if author_time > one_year_ago:
+ one_year_commenters[author_name] += 1
+ discussion_experts_results = DiscussionExpertsResults(
+ authors=authors,
+ commenters=commenters,
+ last_month_commenters=last_month_commenters,
+ three_months_commenters=three_months_commenters,
+ six_months_commenters=six_months_commenters,
+ one_year_commenters=one_year_commenters,
+ )
+ return discussion_experts_results
+
+
+def get_top_users(
+ *,
+ counter: Counter[str],
+ authors: dict[str, Author],
+ skip_users: Container[str],
+ min_count: int = 2,
+) -> list[dict[str, Any]]:
+ users: list[dict[str, Any]] = []
+ for commenter, count in counter.most_common(50):
+ if commenter in skip_users:
+ continue
+ if count >= min_count:
+ author = authors[commenter]
+ users.append(
+ {
+ "login": commenter,
+ "count": count,
+ "avatarUrl": author.avatarUrl,
+ "url": author.url,
+ }
+ )
+ return users
+
+
+def get_users_to_write(
+ *,
+ counter: Counter[str],
+ authors: dict[str, Author],
+ min_count: int = 2,
+) -> list[dict[str, Any]]:
+ users: dict[str, Any] = {}
+ users_list: list[dict[str, Any]] = []
+ for user, count in counter.most_common(60):
+ if count >= min_count:
+ author = authors[user]
+ user_data = {
+ "login": user,
+ "count": count,
+ "avatarUrl": author.avatarUrl,
+ "url": author.url,
+ }
+ users[user] = user_data
+ users_list.append(user_data)
+ return users_list
+
+
+def update_content(*, content_path: Path, new_content: Any) -> bool:
+ old_content = content_path.read_text(encoding="utf-8")
+
+ new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
+ if old_content == new_content:
+ logging.info(f"The content hasn't changed for {content_path}")
+ return False
+ content_path.write_text(new_content, encoding="utf-8")
+ logging.info(f"Updated {content_path}")
+ return True
+
+
+def main() -> None:
+ logging.basicConfig(level=logging.INFO)
+ settings = Settings()
+ logging.info(f"Using config: {settings.model_dump_json()}")
+ g = Github(settings.github_token.get_secret_value())
+ repo = g.get_repo(settings.github_repository)
+
+ discussion_nodes = get_discussion_nodes(settings=settings)
+ experts_results = get_discussions_experts(discussion_nodes=discussion_nodes)
+
+ authors = experts_results.authors
+ maintainers_logins = {"tiangolo"}
+ maintainers = []
+ for login in maintainers_logins:
+ user = authors[login]
+ maintainers.append(
+ {
+ "login": login,
+ "answers": experts_results.commenters[login],
+ "avatarUrl": user.avatarUrl,
+ "url": user.url,
+ }
+ )
+
+ experts = get_users_to_write(
+ counter=experts_results.commenters,
+ authors=authors,
+ )
+ last_month_experts = get_users_to_write(
+ counter=experts_results.last_month_commenters,
+ authors=authors,
+ )
+ three_months_experts = get_users_to_write(
+ counter=experts_results.three_months_commenters,
+ authors=authors,
+ )
+ six_months_experts = get_users_to_write(
+ counter=experts_results.six_months_commenters,
+ authors=authors,
+ )
+ one_year_experts = get_users_to_write(
+ counter=experts_results.one_year_commenters,
+ authors=authors,
+ )
+
+ people = {
+ "maintainers": maintainers,
+ "experts": experts,
+ "last_month_experts": last_month_experts,
+ "three_months_experts": three_months_experts,
+ "six_months_experts": six_months_experts,
+ "one_year_experts": one_year_experts,
+ }
+
+ # For local development
+ # people_path = Path("../docs/en/data/people.yml")
+ people_path = Path("./docs/en/data/people.yml")
+
+ updated = update_content(content_path=people_path, new_content=people)
+
+ if not updated:
+ logging.info("The data hasn't changed, finishing.")
+ return
+
+ logging.info("Setting up GitHub Actions git user")
+ subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
+ subprocess.run(
+ ["git", "config", "user.email", "github-actions@github.com"], check=True
+ )
+ branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}"
+ logging.info(f"Creating a new branch {branch_name}")
+ subprocess.run(["git", "checkout", "-b", branch_name], check=True)
+ logging.info("Adding updated file")
+ subprocess.run(["git", "add", str(people_path)], check=True)
+ logging.info("Committing updated file")
+ message = "๐ฅ Update FastAPI People - Experts"
+ subprocess.run(["git", "commit", "-m", message], check=True)
+ logging.info("Pushing branch")
+ subprocess.run(["git", "push", "origin", branch_name], check=True)
+ logging.info("Creating PR")
+ pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
+ logging.info(f"Created PR: {pr.number}")
+ logging.info("Finished")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py
index 7d914d034..2b7e3457a 100644
--- a/tests/test_security_oauth2.py
+++ b/tests/test_security_oauth2.py
@@ -1,3 +1,4 @@
+import pytest
from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
@@ -137,10 +138,18 @@ def test_strict_login_no_grant_type():
)
-def test_strict_login_incorrect_grant_type():
+@pytest.mark.parametrize(
+ argnames=["grant_type"],
+ argvalues=[
+ pytest.param("incorrect", id="incorrect value"),
+ pytest.param("passwordblah", id="password with suffix"),
+ pytest.param("blahpassword", id="password with prefix"),
+ ],
+)
+def test_strict_login_incorrect_grant_type(grant_type: str):
response = client.post(
"/login",
- data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
+ data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
assert response.json() == IsDict(
@@ -149,9 +158,9 @@ def test_strict_login_incorrect_grant_type():
{
"type": "string_pattern_mismatch",
"loc": ["body", "grant_type"],
- "msg": "String should match pattern 'password'",
- "input": "incorrect",
- "ctx": {"pattern": "password"},
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
}
]
}
@@ -161,9 +170,9 @@ def test_strict_login_incorrect_grant_type():
"detail": [
{
"loc": ["body", "grant_type"],
- "msg": 'string does not match regex "password"',
+ "msg": 'string does not match regex "^password$"',
"type": "value_error.str.regex",
- "ctx": {"pattern": "password"},
+ "ctx": {"pattern": "^password$"},
}
]
}
@@ -248,7 +257,7 @@ def test_openapi_schema():
"properties": {
"grant_type": {
"title": "Grant Type",
- "pattern": "password",
+ "pattern": "^password$",
"type": "string",
},
"username": {"title": "Username", "type": "string"},
diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py
index 0da3b911e..046ac5763 100644
--- a/tests/test_security_oauth2_optional.py
+++ b/tests/test_security_oauth2_optional.py
@@ -1,5 +1,6 @@
from typing import Optional
+import pytest
from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
@@ -141,10 +142,18 @@ def test_strict_login_no_grant_type():
)
-def test_strict_login_incorrect_grant_type():
+@pytest.mark.parametrize(
+ argnames=["grant_type"],
+ argvalues=[
+ pytest.param("incorrect", id="incorrect value"),
+ pytest.param("passwordblah", id="password with suffix"),
+ pytest.param("blahpassword", id="password with prefix"),
+ ],
+)
+def test_strict_login_incorrect_grant_type(grant_type: str):
response = client.post(
"/login",
- data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
+ data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
assert response.json() == IsDict(
@@ -153,9 +162,9 @@ def test_strict_login_incorrect_grant_type():
{
"type": "string_pattern_mismatch",
"loc": ["body", "grant_type"],
- "msg": "String should match pattern 'password'",
- "input": "incorrect",
- "ctx": {"pattern": "password"},
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
}
]
}
@@ -165,9 +174,9 @@ def test_strict_login_incorrect_grant_type():
"detail": [
{
"loc": ["body", "grant_type"],
- "msg": 'string does not match regex "password"',
+ "msg": 'string does not match regex "^password$"',
"type": "value_error.str.regex",
- "ctx": {"pattern": "password"},
+ "ctx": {"pattern": "^password$"},
}
]
}
@@ -252,7 +261,7 @@ def test_openapi_schema():
"properties": {
"grant_type": {
"title": "Grant Type",
- "pattern": "password",
+ "pattern": "^password$",
"type": "string",
},
"username": {"title": "Username", "type": "string"},
diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py
index 85a9f9b39..629cddca2 100644
--- a/tests/test_security_oauth2_optional_description.py
+++ b/tests/test_security_oauth2_optional_description.py
@@ -1,5 +1,6 @@
from typing import Optional
+import pytest
from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
@@ -142,10 +143,18 @@ def test_strict_login_no_grant_type():
)
-def test_strict_login_incorrect_grant_type():
+@pytest.mark.parametrize(
+ argnames=["grant_type"],
+ argvalues=[
+ pytest.param("incorrect", id="incorrect value"),
+ pytest.param("passwordblah", id="password with suffix"),
+ pytest.param("blahpassword", id="password with prefix"),
+ ],
+)
+def test_strict_login_incorrect_grant_type(grant_type: str):
response = client.post(
"/login",
- data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
+ data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
assert response.json() == IsDict(
@@ -154,9 +163,9 @@ def test_strict_login_incorrect_grant_type():
{
"type": "string_pattern_mismatch",
"loc": ["body", "grant_type"],
- "msg": "String should match pattern 'password'",
- "input": "incorrect",
- "ctx": {"pattern": "password"},
+ "msg": "String should match pattern '^password$'",
+ "input": grant_type,
+ "ctx": {"pattern": "^password$"},
}
]
}
@@ -166,9 +175,9 @@ def test_strict_login_incorrect_grant_type():
"detail": [
{
"loc": ["body", "grant_type"],
- "msg": 'string does not match regex "password"',
+ "msg": 'string does not match regex "^password$"',
"type": "value_error.str.regex",
- "ctx": {"pattern": "password"},
+ "ctx": {"pattern": "^password$"},
}
]
}
@@ -253,7 +262,7 @@ def test_openapi_schema():
"properties": {
"grant_type": {
"title": "Grant Type",
- "pattern": "password",
+ "pattern": "^password$",
"type": "string",
},
"username": {"title": "Username", "type": "string"},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
index c26f8b89b..d18ceae48 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
@@ -1,13 +1,26 @@
+import importlib
+
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
+from ...utils import needs_py39, needs_py310
+
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.body_multiple_params.tutorial003 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial003",
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ "tutorial003_an",
+ pytest.param("tutorial003_an_py39", marks=needs_py39),
+ pytest.param("tutorial003_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py
deleted file mode 100644
index 62c7e2fad..000000000
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py
+++ /dev/null
@@ -1,273 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.body_multiple_params.tutorial003_an import app
-
- client = TestClient(app)
- return client
-
-
-def test_post_body_valid(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "importance": 2,
- "item": {"name": "Foo", "price": 50.5},
- "user": {"username": "Dave"},
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "item_id": 5,
- "importance": 2,
- "item": {
- "name": "Foo",
- "price": 50.5,
- "description": None,
- "tax": None,
- },
- "user": {"username": "Dave", "full_name": None},
- }
-
-
-def test_post_body_no_data(client: TestClient):
- response = client.put("/items/5", json=None)
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_post_body_empty_list(client: TestClient):
- response = client.put("/items/5", json=[])
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "put": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
- }
- }
- },
- "required": True,
- },
- }
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"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"}
- ),
- },
- },
- "User": {
- "title": "User",
- "required": ["username"],
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- },
- },
- "Body_update_item_items__item_id__put": {
- "title": "Body_update_item_items__item_id__put",
- "required": ["item", "user", "importance"],
- "type": "object",
- "properties": {
- "item": {"$ref": "#/components/schemas/Item"},
- "user": {"$ref": "#/components/schemas/User"},
- "importance": {"title": "Importance", "type": "integer"},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py
deleted file mode 100644
index f46430fb5..000000000
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py
+++ /dev/null
@@ -1,279 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.body_multiple_params.tutorial003_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_post_body_valid(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "importance": 2,
- "item": {"name": "Foo", "price": 50.5},
- "user": {"username": "Dave"},
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "item_id": 5,
- "importance": 2,
- "item": {
- "name": "Foo",
- "price": 50.5,
- "description": None,
- "tax": None,
- },
- "user": {"username": "Dave", "full_name": None},
- }
-
-
-@needs_py310
-def test_post_body_no_data(client: TestClient):
- response = client.put("/items/5", json=None)
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-@needs_py310
-def test_post_body_empty_list(client: TestClient):
- response = client.put("/items/5", json=[])
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "put": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
- }
- }
- },
- "required": True,
- },
- }
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"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"}
- ),
- },
- },
- "User": {
- "title": "User",
- "required": ["username"],
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- },
- },
- "Body_update_item_items__item_id__put": {
- "title": "Body_update_item_items__item_id__put",
- "required": ["item", "user", "importance"],
- "type": "object",
- "properties": {
- "item": {"$ref": "#/components/schemas/Item"},
- "user": {"$ref": "#/components/schemas/User"},
- "importance": {"title": "Importance", "type": "integer"},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py
deleted file mode 100644
index 29071cddc..000000000
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py
+++ /dev/null
@@ -1,279 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.body_multiple_params.tutorial003_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_post_body_valid(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "importance": 2,
- "item": {"name": "Foo", "price": 50.5},
- "user": {"username": "Dave"},
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "item_id": 5,
- "importance": 2,
- "item": {
- "name": "Foo",
- "price": 50.5,
- "description": None,
- "tax": None,
- },
- "user": {"username": "Dave", "full_name": None},
- }
-
-
-@needs_py39
-def test_post_body_no_data(client: TestClient):
- response = client.put("/items/5", json=None)
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-@needs_py39
-def test_post_body_empty_list(client: TestClient):
- response = client.put("/items/5", json=[])
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "put": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
- }
- }
- },
- "required": True,
- },
- }
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"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"}
- ),
- },
- },
- "User": {
- "title": "User",
- "required": ["username"],
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- },
- },
- "Body_update_item_items__item_id__put": {
- "title": "Body_update_item_items__item_id__put",
- "required": ["item", "user", "importance"],
- "type": "object",
- "properties": {
- "item": {"$ref": "#/components/schemas/Item"},
- "user": {"$ref": "#/components/schemas/User"},
- "importance": {"title": "Importance", "type": "integer"},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
deleted file mode 100644
index 133afe9b5..000000000
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
+++ /dev/null
@@ -1,279 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.body_multiple_params.tutorial003_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_post_body_valid(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "importance": 2,
- "item": {"name": "Foo", "price": 50.5},
- "user": {"username": "Dave"},
- },
- )
- assert response.status_code == 200
- assert response.json() == {
- "item_id": 5,
- "importance": 2,
- "item": {
- "name": "Foo",
- "price": 50.5,
- "description": None,
- "tax": None,
- },
- "user": {"username": "Dave", "full_name": None},
- }
-
-
-@needs_py310
-def test_post_body_no_data(client: TestClient):
- response = client.put("/items/5", json=None)
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-@needs_py310
-def test_post_body_empty_list(client: TestClient):
- response = client.put("/items/5", json=[])
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "item"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "user"],
- "msg": "Field required",
- "input": None,
- },
- {
- "type": "missing",
- "loc": ["body", "importance"],
- "msg": "Field required",
- "input": None,
- },
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "item"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "user"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- {
- "loc": ["body", "importance"],
- "msg": "field required",
- "type": "value_error.missing",
- },
- ]
- }
- )
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "put": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
- }
- }
- },
- "required": True,
- },
- }
- }
- },
- "components": {
- "schemas": {
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"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"}
- ),
- },
- },
- "User": {
- "title": "User",
- "required": ["username"],
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- },
- },
- "Body_update_item_items__item_id__put": {
- "title": "Body_update_item_items__item_id__put",
- "required": ["item", "user", "importance"],
- "type": "object",
- "properties": {
- "item": {"$ref": "#/components/schemas/Item"},
- "user": {"$ref": "#/components/schemas/User"},
- "importance": {"title": "Importance", "type": "integer"},
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
index 166901188..d06a385b5 100644
--- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
+++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py
@@ -12,7 +12,7 @@ def test_swagger_ui():
'"syntaxHighlight": false' not in response.text
), "not used parameters should not be included"
assert (
- '"syntaxHighlight.theme": "obsidian"' in response.text
+ '"syntaxHighlight": {"theme": "obsidian"}' in response.text
), "parameters with middle dots should be included in a JSON compatible way"
assert (
'"dom_id": "#swagger-ui"' in response.text
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py
index 4f52d6ff7..e08e16963 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py
@@ -1,14 +1,29 @@
+import importlib
+
import pytest
from dirty_equals import IsDict
from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
from fastapi.testclient import TestClient
+from ...utils import needs_py39, needs_py310
+
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial010 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial010",
+ pytest.param("tutorial010_py310", marks=needs_py310),
+ "tutorial010_an",
+ pytest.param("tutorial010_an_py39", marks=needs_py39),
+ pytest.param("tutorial010_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py
deleted file mode 100644
index 5daca1e70..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py
+++ /dev/null
@@ -1,168 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
-from fastapi.testclient import TestClient
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial010_an import app
-
- client = TestClient(app)
- return client
-
-
-def test_query_params_str_validations_no_query(client: TestClient):
- response = client.get("/items/")
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-def test_query_params_str_validations_item_query_fixedquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {
- "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
- "q": "fixedquery",
- }
-
-
-def test_query_params_str_validations_q_fixedquery(client: TestClient):
- response = client.get("/items/", params={"q": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-def test_query_params_str_validations_item_query_nonregexquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "nonregexquery"})
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["query", "item-query"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["query", "item-query"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
-
-
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "description": "Query string for the items to search in the database that have a good match",
- "required": False,
- "deprecated": True,
- "schema": IsDict(
- {
- "anyOf": [
- {
- "type": "string",
- "minLength": 3,
- "maxLength": 50,
- "pattern": "^fixedquery$",
- },
- {"type": "null"},
- ],
- "title": "Query string",
- "description": "Query string for the items to search in the database that have a good match",
- # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
- **(
- {"deprecated": True}
- if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10)
- else {}
- ),
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Query string",
- "maxLength": 50,
- "minLength": 3,
- "pattern": "^fixedquery$",
- "type": "string",
- "description": "Query string for the items to search in the database that have a good match",
- }
- ),
- "name": "item-query",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py
deleted file mode 100644
index 89da4d82e..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py
+++ /dev/null
@@ -1,175 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial010_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_query_params_str_validations_no_query(client: TestClient):
- response = client.get("/items/")
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-@needs_py310
-def test_query_params_str_validations_item_query_fixedquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {
- "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
- "q": "fixedquery",
- }
-
-
-@needs_py310
-def test_query_params_str_validations_q_fixedquery(client: TestClient):
- response = client.get("/items/", params={"q": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-@needs_py310
-def test_query_params_str_validations_item_query_nonregexquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "nonregexquery"})
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["query", "item-query"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["query", "item-query"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "description": "Query string for the items to search in the database that have a good match",
- "required": False,
- "deprecated": True,
- "schema": IsDict(
- {
- "anyOf": [
- {
- "type": "string",
- "minLength": 3,
- "maxLength": 50,
- "pattern": "^fixedquery$",
- },
- {"type": "null"},
- ],
- "title": "Query string",
- "description": "Query string for the items to search in the database that have a good match",
- # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
- **(
- {"deprecated": True}
- if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10)
- else {}
- ),
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Query string",
- "maxLength": 50,
- "minLength": 3,
- "pattern": "^fixedquery$",
- "type": "string",
- "description": "Query string for the items to search in the database that have a good match",
- }
- ),
- "name": "item-query",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py
deleted file mode 100644
index f5f692b06..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py
+++ /dev/null
@@ -1,175 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial010_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_query_params_str_validations_no_query(client: TestClient):
- response = client.get("/items/")
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-@needs_py39
-def test_query_params_str_validations_item_query_fixedquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {
- "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
- "q": "fixedquery",
- }
-
-
-@needs_py39
-def test_query_params_str_validations_q_fixedquery(client: TestClient):
- response = client.get("/items/", params={"q": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-@needs_py39
-def test_query_params_str_validations_item_query_nonregexquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "nonregexquery"})
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["query", "item-query"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["query", "item-query"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "description": "Query string for the items to search in the database that have a good match",
- "required": False,
- "deprecated": True,
- "schema": IsDict(
- {
- "anyOf": [
- {
- "type": "string",
- "minLength": 3,
- "maxLength": 50,
- "pattern": "^fixedquery$",
- },
- {"type": "null"},
- ],
- "title": "Query string",
- "description": "Query string for the items to search in the database that have a good match",
- # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
- **(
- {"deprecated": True}
- if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10)
- else {}
- ),
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Query string",
- "maxLength": 50,
- "minLength": 3,
- "pattern": "^fixedquery$",
- "type": "string",
- "description": "Query string for the items to search in the database that have a good match",
- }
- ),
- "name": "item-query",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py
deleted file mode 100644
index 5b62c969f..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py
+++ /dev/null
@@ -1,175 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial010_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_query_params_str_validations_no_query(client: TestClient):
- response = client.get("/items/")
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-@needs_py310
-def test_query_params_str_validations_item_query_fixedquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {
- "items": [{"item_id": "Foo"}, {"item_id": "Bar"}],
- "q": "fixedquery",
- }
-
-
-@needs_py310
-def test_query_params_str_validations_q_fixedquery(client: TestClient):
- response = client.get("/items/", params={"q": "fixedquery"})
- assert response.status_code == 200
- assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
-
-
-@needs_py310
-def test_query_params_str_validations_item_query_nonregexquery(client: TestClient):
- response = client.get("/items/", params={"item-query": "nonregexquery"})
- assert response.status_code == 422
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "string_pattern_mismatch",
- "loc": ["query", "item-query"],
- "msg": "String should match pattern '^fixedquery$'",
- "input": "nonregexquery",
- "ctx": {"pattern": "^fixedquery$"},
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "ctx": {"pattern": "^fixedquery$"},
- "loc": ["query", "item-query"],
- "msg": 'string does not match regex "^fixedquery$"',
- "type": "value_error.str.regex",
- }
- ]
- }
- )
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "description": "Query string for the items to search in the database that have a good match",
- "required": False,
- "deprecated": True,
- "schema": IsDict(
- {
- "anyOf": [
- {
- "type": "string",
- "minLength": 3,
- "maxLength": 50,
- "pattern": "^fixedquery$",
- },
- {"type": "null"},
- ],
- "title": "Query string",
- "description": "Query string for the items to search in the database that have a good match",
- # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
- **(
- {"deprecated": True}
- if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10)
- else {}
- ),
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Query string",
- "maxLength": 50,
- "minLength": 3,
- "pattern": "^fixedquery$",
- "type": "string",
- "description": "Query string for the items to search in the database that have a good match",
- }
- ),
- "name": "item-query",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
index 5ba39b05d..f4da25752 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
@@ -1,26 +1,47 @@
+import importlib
+
+import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.query_params_str_validations.tutorial011 import app
+from ...utils import needs_py39, needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial011",
+ pytest.param("tutorial011_py39", marks=needs_py310),
+ pytest.param("tutorial011_py310", marks=needs_py310),
+ "tutorial011_an",
+ pytest.param("tutorial011_an_py39", marks=needs_py39),
+ pytest.param("tutorial011_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_multi_query_values():
+def test_multi_query_values(client: TestClient):
url = "/items/?q=foo&q=bar"
response = client.get(url)
assert response.status_code == 200, response.text
assert response.json() == {"q": ["foo", "bar"]}
-def test_query_no_values():
+def test_query_no_values(client: TestClient):
url = "/items/"
response = client.get(url)
assert response.status_code == 200, response.text
assert response.json() == {"q": None}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py
deleted file mode 100644
index 3942ea77a..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py
+++ /dev/null
@@ -1,108 +0,0 @@
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from docs_src.query_params_str_validations.tutorial011_an import app
-
-client = TestClient(app)
-
-
-def test_multi_query_values():
- url = "/items/?q=foo&q=bar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-def test_query_no_values():
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": None}
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "array", "items": {"type": "string"}},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py
deleted file mode 100644
index f2ec38c95..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py
+++ /dev/null
@@ -1,118 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial011_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_multi_query_values(client: TestClient):
- url = "/items/?q=foo&q=bar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-@needs_py310
-def test_query_no_values(client: TestClient):
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": None}
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "array", "items": {"type": "string"}},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py
deleted file mode 100644
index cd7b15679..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py
+++ /dev/null
@@ -1,118 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial011_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_multi_query_values(client: TestClient):
- url = "/items/?q=foo&q=bar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-@needs_py39
-def test_query_no_values(client: TestClient):
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": None}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "array", "items": {"type": "string"}},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
deleted file mode 100644
index bdc729516..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
+++ /dev/null
@@ -1,118 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial011_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_multi_query_values(client: TestClient):
- url = "/items/?q=foo&q=bar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-@needs_py310
-def test_query_no_values(client: TestClient):
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": None}
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "array", "items": {"type": "string"}},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
deleted file mode 100644
index 26ac56b2f..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
+++ /dev/null
@@ -1,118 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial011_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_multi_query_values(client: TestClient):
- url = "/items/?q=foo&q=bar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-@needs_py39
-def test_query_no_values(client: TestClient):
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": None}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": IsDict(
- {
- "anyOf": [
- {"type": "array", "items": {"type": "string"}},
- {"type": "null"},
- ],
- "title": "Q",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- }
- ),
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
index 1436db384..549a90519 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
@@ -1,25 +1,44 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.query_params_str_validations.tutorial012 import app
+from ...utils import needs_py39
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial012",
+ pytest.param("tutorial012_py39", marks=needs_py39),
+ "tutorial012_an",
+ pytest.param("tutorial012_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_default_query_values():
+def test_default_query_values(client: TestClient):
url = "/items/"
response = client.get(url)
assert response.status_code == 200, response.text
assert response.json() == {"q": ["foo", "bar"]}
-def test_multi_query_values():
+def test_multi_query_values(client: TestClient):
url = "/items/?q=baz&q=foobar"
response = client.get(url)
assert response.status_code == 200, response.text
assert response.json() == {"q": ["baz", "foobar"]}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py
deleted file mode 100644
index 270763f1d..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from fastapi.testclient import TestClient
-
-from docs_src.query_params_str_validations.tutorial012_an import app
-
-client = TestClient(app)
-
-
-def test_default_query_values():
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-def test_multi_query_values():
- url = "/items/?q=baz&q=foobar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["baz", "foobar"]}
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- "default": ["foo", "bar"],
- },
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py
deleted file mode 100644
index 548391683..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial012_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_default_query_values(client: TestClient):
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-@needs_py39
-def test_multi_query_values(client: TestClient):
- url = "/items/?q=baz&q=foobar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["baz", "foobar"]}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- "default": ["foo", "bar"],
- },
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
deleted file mode 100644
index e7d745154..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial012_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_default_query_values(client: TestClient):
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-@needs_py39
-def test_multi_query_values(client: TestClient):
- url = "/items/?q=baz&q=foobar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["baz", "foobar"]}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": {
- "title": "Q",
- "type": "array",
- "items": {"type": "string"},
- "default": ["foo", "bar"],
- },
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
index 1ba1fdf61..f2f5f7a85 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
@@ -1,25 +1,43 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.query_params_str_validations.tutorial013 import app
+from ...utils import needs_py39
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial013",
+ "tutorial013_an",
+ pytest.param("tutorial013_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_multi_query_values():
+def test_multi_query_values(client: TestClient):
url = "/items/?q=foo&q=bar"
response = client.get(url)
assert response.status_code == 200, response.text
assert response.json() == {"q": ["foo", "bar"]}
-def test_query_no_values():
+def test_query_no_values(client: TestClient):
url = "/items/"
response = client.get(url)
assert response.status_code == 200, response.text
assert response.json() == {"q": []}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py
deleted file mode 100644
index 343261748..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from fastapi.testclient import TestClient
-
-from docs_src.query_params_str_validations.tutorial013_an import app
-
-client = TestClient(app)
-
-
-def test_multi_query_values():
- url = "/items/?q=foo&q=bar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-def test_query_no_values():
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": []}
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": {
- "title": "Q",
- "type": "array",
- "items": {},
- "default": [],
- },
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py
deleted file mode 100644
index 537d6325b..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial013_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_multi_query_values(client: TestClient):
- url = "/items/?q=foo&q=bar"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": ["foo", "bar"]}
-
-
-@needs_py39
-def test_query_no_values(client: TestClient):
- url = "/items/"
- response = client.get(url)
- assert response.status_code == 200, response.text
- assert response.json() == {"q": []}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "parameters": [
- {
- "required": False,
- "schema": {
- "title": "Q",
- "type": "array",
- "items": {},
- "default": [],
- },
- "name": "q",
- "in": "query",
- }
- ],
- }
- }
- },
- "components": {
- "schemas": {
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
index 7bce7590c..edd40bb1a 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
@@ -1,23 +1,43 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.query_params_str_validations.tutorial014 import app
+from ...utils import needs_py39, needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial014",
+ pytest.param("tutorial014_py310", marks=needs_py310),
+ "tutorial014_an",
+ pytest.param("tutorial014_an_py39", marks=needs_py39),
+ pytest.param("tutorial014_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(
+ f"docs_src.query_params_str_validations.{request.param}"
+ )
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_hidden_query():
+def test_hidden_query(client: TestClient):
response = client.get("/items?hidden_query=somevalue")
assert response.status_code == 200, response.text
assert response.json() == {"hidden_query": "somevalue"}
-def test_no_hidden_query():
+def test_no_hidden_query(client: TestClient):
response = client.get("/items")
assert response.status_code == 200, response.text
assert response.json() == {"hidden_query": "Not found"}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py
deleted file mode 100644
index 2182e87b7..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from fastapi.testclient import TestClient
-
-from docs_src.query_params_str_validations.tutorial014_an import app
-
-client = TestClient(app)
-
-
-def test_hidden_query():
- response = client.get("/items?hidden_query=somevalue")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "somevalue"}
-
-
-def test_no_hidden_query():
- response = client.get("/items")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "Not found"}
-
-
-def test_openapi_schema():
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py
deleted file mode 100644
index 344004d01..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial014_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_hidden_query(client: TestClient):
- response = client.get("/items?hidden_query=somevalue")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "somevalue"}
-
-
-@needs_py310
-def test_no_hidden_query(client: TestClient):
- response = client.get("/items")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "Not found"}
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py
deleted file mode 100644
index 5d4f6df3d..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial014_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_hidden_query(client: TestClient):
- response = client.get("/items?hidden_query=somevalue")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "somevalue"}
-
-
-@needs_py310
-def test_no_hidden_query(client: TestClient):
- response = client.get("/items")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "Not found"}
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
deleted file mode 100644
index dad49fb12..000000000
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.query_params_str_validations.tutorial014_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_hidden_query(client: TestClient):
- response = client.get("/items?hidden_query=somevalue")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "somevalue"}
-
-
-@needs_py310
-def test_no_hidden_query(client: TestClient):
- response = client.get("/items")
- assert response.status_code == 200, response.text
- assert response.json() == {"hidden_query": "Not found"}
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py
index f5817593b..b06919961 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001.py
@@ -1,23 +1,28 @@
+import importlib
+
+import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.request_files.tutorial001 import app
+from ...utils import needs_py39
-client = TestClient(app)
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001",
+ "tutorial001_an",
+ pytest.param("tutorial001_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.request_files.{request.param}")
-file_required = {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
-}
+ client = TestClient(mod.app)
+ return client
-def test_post_form_no_body():
+def test_post_form_no_body(client: TestClient):
response = client.post("/files/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
@@ -45,7 +50,7 @@ def test_post_form_no_body():
)
-def test_post_body_json():
+def test_post_body_json(client: TestClient):
response = client.post("/files/", json={"file": "Foo"})
assert response.status_code == 422, response.text
assert response.json() == IsDict(
@@ -73,41 +78,38 @@ def test_post_body_json():
)
-def test_post_file(tmp_path):
+def test_post_file(tmp_path, client: TestClient):
path = tmp_path / "test.txt"
path.write_bytes(b"")
- client = TestClient(app)
with path.open("rb") as file:
response = client.post("/files/", files={"file": file})
assert response.status_code == 200, response.text
assert response.json() == {"file_size": 14}
-def test_post_large_file(tmp_path):
+def test_post_large_file(tmp_path, client: TestClient):
default_pydantic_max_size = 2**16
path = tmp_path / "test.txt"
path.write_bytes(b"x" * (default_pydantic_max_size + 1))
- client = TestClient(app)
with path.open("rb") as file:
response = client.post("/files/", files={"file": file})
assert response.status_code == 200, response.text
assert response.json() == {"file_size": default_pydantic_max_size + 1}
-def test_post_upload_file(tmp_path):
+def test_post_upload_file(tmp_path, client: TestClient):
path = tmp_path / "test.txt"
path.write_bytes(b"")
- client = TestClient(app)
with path.open("rb") as file:
response = client.post("/uploadfile/", files={"file": file})
assert response.status_code == 200, response.text
assert response.json() == {"filename": "test.txt"}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
index 42f75442a..9075a1756 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py
@@ -1,46 +1,63 @@
+import importlib
+from pathlib import Path
+
+import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.request_files.tutorial001_02 import app
+from ...utils import needs_py39, needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001_02",
+ pytest.param("tutorial001_02_py310", marks=needs_py310),
+ "tutorial001_02_an",
+ pytest.param("tutorial001_02_an_py39", marks=needs_py39),
+ pytest.param("tutorial001_02_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.request_files.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_post_form_no_body():
+def test_post_form_no_body(client: TestClient):
response = client.post("/files/")
assert response.status_code == 200, response.text
assert response.json() == {"message": "No file sent"}
-def test_post_uploadfile_no_body():
+def test_post_uploadfile_no_body(client: TestClient):
response = client.post("/uploadfile/")
assert response.status_code == 200, response.text
assert response.json() == {"message": "No upload file sent"}
-def test_post_file(tmp_path):
+def test_post_file(tmp_path: Path, client: TestClient):
path = tmp_path / "test.txt"
path.write_bytes(b"")
- client = TestClient(app)
with path.open("rb") as file:
response = client.post("/files/", files={"file": file})
assert response.status_code == 200, response.text
assert response.json() == {"file_size": 14}
-def test_post_upload_file(tmp_path):
+def test_post_upload_file(tmp_path: Path, client: TestClient):
path = tmp_path / "test.txt"
path.write_bytes(b"")
- client = TestClient(app)
with path.open("rb") as file:
response = client.post("/uploadfile/", files={"file": file})
assert response.status_code == 200, response.text
assert response.json() == {"filename": "test.txt"}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py
deleted file mode 100644
index f63eb339c..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py
+++ /dev/null
@@ -1,208 +0,0 @@
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from docs_src.request_files.tutorial001_02_an import app
-
-client = TestClient(app)
-
-
-def test_post_form_no_body():
- response = client.post("/files/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No file sent"}
-
-
-def test_post_uploadfile_no_body():
- response = client.post("/uploadfile/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No upload file sent"}
-
-
-def test_post_file(tmp_path):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- client = TestClient(app)
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-def test_post_upload_file(tmp_path):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- client = TestClient(app)
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-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": {
- "/files/": {
- "post": {
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py
deleted file mode 100644
index 94b6ac67e..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py
+++ /dev/null
@@ -1,220 +0,0 @@
-from pathlib import Path
-
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_files.tutorial001_02_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_post_form_no_body(client: TestClient):
- response = client.post("/files/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No file sent"}
-
-
-@needs_py310
-def test_post_uploadfile_no_body(client: TestClient):
- response = client.post("/uploadfile/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No upload file sent"}
-
-
-@needs_py310
-def test_post_file(tmp_path: Path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-@needs_py310
-def test_post_upload_file(tmp_path: Path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/files/": {
- "post": {
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py
deleted file mode 100644
index fcb39f8f1..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py
+++ /dev/null
@@ -1,220 +0,0 @@
-from pathlib import Path
-
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_files.tutorial001_02_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_post_form_no_body(client: TestClient):
- response = client.post("/files/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No file sent"}
-
-
-@needs_py39
-def test_post_uploadfile_no_body(client: TestClient):
- response = client.post("/uploadfile/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No upload file sent"}
-
-
-@needs_py39
-def test_post_file(tmp_path: Path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-@needs_py39
-def test_post_upload_file(tmp_path: Path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/files/": {
- "post": {
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py
deleted file mode 100644
index a700752a3..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py
+++ /dev/null
@@ -1,220 +0,0 @@
-from pathlib import Path
-
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_files.tutorial001_02_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_post_form_no_body(client: TestClient):
- response = client.post("/files/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No file sent"}
-
-
-@needs_py310
-def test_post_uploadfile_no_body(client: TestClient):
- response = client.post("/uploadfile/")
- assert response.status_code == 200, response.text
- assert response.json() == {"message": "No upload file sent"}
-
-
-@needs_py310
-def test_post_file(tmp_path: Path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-@needs_py310
-def test_post_upload_file(tmp_path: Path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/files/": {
- "post": {
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": IsDict(
- {
- "allOf": [
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- ],
- "title": "Body",
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- )
- }
- }
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "type": "object",
- "properties": {
- "file": IsDict(
- {
- "title": "File",
- "anyOf": [
- {"type": "string", "format": "binary"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "File", "type": "string", "format": "binary"}
- )
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py
index f02170814..9fbe2166c 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py
@@ -1,33 +1,47 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.request_files.tutorial001_03 import app
+from ...utils import needs_py39
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001_03",
+ "tutorial001_03_an",
+ pytest.param("tutorial001_03_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.request_files.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_post_file(tmp_path):
+def test_post_file(tmp_path, client: TestClient):
path = tmp_path / "test.txt"
path.write_bytes(b"")
- client = TestClient(app)
with path.open("rb") as file:
response = client.post("/files/", files={"file": file})
assert response.status_code == 200, response.text
assert response.json() == {"file_size": 14}
-def test_post_upload_file(tmp_path):
+def test_post_upload_file(tmp_path, client: TestClient):
path = tmp_path / "test.txt"
path.write_bytes(b"")
- client = TestClient(app)
with path.open("rb") as file:
response = client.post("/uploadfile/", files={"file": file})
assert response.status_code == 200, response.text
assert response.json() == {"filename": "test.txt"}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py
deleted file mode 100644
index acfb749ce..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py
+++ /dev/null
@@ -1,159 +0,0 @@
-from fastapi.testclient import TestClient
-
-from docs_src.request_files.tutorial001_03_an import app
-
-client = TestClient(app)
-
-
-def test_post_file(tmp_path):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- client = TestClient(app)
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-def test_post_upload_file(tmp_path):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- client = TestClient(app)
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-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": {
- "/files/": {
- "post": {
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {
- "title": "File",
- "type": "string",
- "description": "A file read as bytes",
- "format": "binary",
- }
- },
- },
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {
- "title": "File",
- "type": "string",
- "description": "A file read as UploadFile",
- "format": "binary",
- }
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py
deleted file mode 100644
index 36e5faac1..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py
+++ /dev/null
@@ -1,167 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_files.tutorial001_03_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_post_file(tmp_path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-@needs_py39
-def test_post_upload_file(tmp_path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/files/": {
- "post": {
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- }
- },
- "required": True,
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {
- "title": "File",
- "type": "string",
- "description": "A file read as bytes",
- "format": "binary",
- }
- },
- },
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {
- "title": "File",
- "type": "string",
- "description": "A file read as UploadFile",
- "format": "binary",
- }
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_an.py
deleted file mode 100644
index 1c78e3679..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_an.py
+++ /dev/null
@@ -1,218 +0,0 @@
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from docs_src.request_files.tutorial001_an import app
-
-client = TestClient(app)
-
-
-def test_post_form_no_body():
- response = client.post("/files/")
- assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_post_body_json():
- response = client.post("/files/", json={"file": "Foo"})
- assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-def test_post_file(tmp_path):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- client = TestClient(app)
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-def test_post_large_file(tmp_path):
- default_pydantic_max_size = 2**16
- path = tmp_path / "test.txt"
- path.write_bytes(b"x" * (default_pydantic_max_size + 1))
-
- client = TestClient(app)
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": default_pydantic_max_size + 1}
-
-
-def test_post_upload_file(tmp_path):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- client = TestClient(app)
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-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": {
- "/files/": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- }
- },
- "required": True,
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {"title": "File", "type": "string", "format": "binary"}
- },
- },
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {"title": "File", "type": "string", "format": "binary"}
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py
deleted file mode 100644
index 843fcec28..000000000
--- a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py
+++ /dev/null
@@ -1,228 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_files.tutorial001_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_post_form_no_body(client: TestClient):
- response = client.post("/files/")
- assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-@needs_py39
-def test_post_body_json(client: TestClient):
- response = client.post("/files/", json={"file": "Foo"})
- assert response.status_code == 422, response.text
- assert response.json() == IsDict(
- {
- "detail": [
- {
- "type": "missing",
- "loc": ["body", "file"],
- "msg": "Field required",
- "input": None,
- }
- ]
- }
- ) | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "detail": [
- {
- "loc": ["body", "file"],
- "msg": "field required",
- "type": "value_error.missing",
- }
- ]
- }
- )
-
-
-@needs_py39
-def test_post_file(tmp_path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": 14}
-
-
-@needs_py39
-def test_post_large_file(tmp_path, client: TestClient):
- default_pydantic_max_size = 2**16
- path = tmp_path / "test.txt"
- path.write_bytes(b"x" * (default_pydantic_max_size + 1))
-
- with path.open("rb") as file:
- response = client.post("/files/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"file_size": default_pydantic_max_size + 1}
-
-
-@needs_py39
-def test_post_upload_file(tmp_path, client: TestClient):
- path = tmp_path / "test.txt"
- path.write_bytes(b"")
-
- with path.open("rb") as file:
- response = client.post("/uploadfile/", files={"file": file})
- assert response.status_code == 200, response.text
- assert response.json() == {"filename": "test.txt"}
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/files/": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Create File",
- "operationId": "create_file_files__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_file_files__post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/uploadfile/": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Create Upload File",
- "operationId": "create_upload_file_uploadfile__post",
- "requestBody": {
- "content": {
- "multipart/form-data": {
- "schema": {
- "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
- }
- }
- },
- "required": True,
- },
- }
- },
- },
- "components": {
- "schemas": {
- "Body_create_upload_file_uploadfile__post": {
- "title": "Body_create_upload_file_uploadfile__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {"title": "File", "type": "string", "format": "binary"}
- },
- },
- "Body_create_file_files__post": {
- "title": "Body_create_file_files__post",
- "required": ["file"],
- "type": "object",
- "properties": {
- "file": {"title": "File", "type": "string", "format": "binary"}
- },
- },
- "ValidationError": {
- "title": "ValidationError",
- "required": ["loc", "msg", "type"],
- "type": "object",
- "properties": {
- "loc": {
- "title": "Location",
- "type": "array",
- "items": {
- "anyOf": [{"type": "string"}, {"type": "integer"}]
- },
- },
- "msg": {"title": "Message", "type": "string"},
- "type": {"title": "Error Type", "type": "string"},
- },
- },
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py
index db1552e5c..446a87657 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial002.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial002.py
@@ -1,12 +1,35 @@
+import importlib
+
+import pytest
from dirty_equals import IsDict
+from fastapi import FastAPI
from fastapi.testclient import TestClient
-from docs_src.request_files.tutorial002 import app
+from ...utils import needs_py39
+
+
+@pytest.fixture(
+ name="app",
+ params=[
+ "tutorial002",
+ "tutorial002_an",
+ pytest.param("tutorial002_py39", marks=needs_py39),
+ pytest.param("tutorial002_an_py39", marks=needs_py39),
+ ],
+)
+def get_app(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.request_files.{request.param}")
-client = TestClient(app)
+ return mod.app
+
+
+@pytest.fixture(name="client")
+def get_client(app: FastAPI):
+ client = TestClient(app)
+ return client
-def test_post_form_no_body():
+def test_post_form_no_body(client: TestClient):
response = client.post("/files/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
@@ -34,7 +57,7 @@ def test_post_form_no_body():
)
-def test_post_body_json():
+def test_post_body_json(client: TestClient):
response = client.post("/files/", json={"file": "Foo"})
assert response.status_code == 422, response.text
assert response.json() == IsDict(
@@ -62,7 +85,7 @@ def test_post_body_json():
)
-def test_post_files(tmp_path):
+def test_post_files(tmp_path, app: FastAPI):
path = tmp_path / "test.txt"
path.write_bytes(b"")
path2 = tmp_path / "test2.txt"
@@ -81,7 +104,7 @@ def test_post_files(tmp_path):
assert response.json() == {"file_sizes": [14, 15]}
-def test_post_upload_file(tmp_path):
+def test_post_upload_file(tmp_path, app: FastAPI):
path = tmp_path / "test.txt"
path.write_bytes(b"")
path2 = tmp_path / "test2.txt"
@@ -100,14 +123,14 @@ def test_post_upload_file(tmp_path):
assert response.json() == {"filenames": ["test.txt", "test2.txt"]}
-def test_get_root():
+def test_get_root(app: FastAPI):
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200, response.text
assert b"