Browse Source

Merge branch 'master' into multiple-query-params-models

pull/12944/head
Yurii Karabas 6 months ago
committed by GitHub
parent
commit
edc1eac529
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      .github/actions/notify-translations/Dockerfile
  2. 10
      .github/actions/notify-translations/action.yml
  3. 7
      .github/actions/people/Dockerfile
  4. 10
      .github/actions/people/action.yml
  5. 682
      .github/actions/people/app/main.py
  6. 20
      .github/workflows/notify-translations.yml
  7. 37
      .github/workflows/people.yml
  8. 2
      .github/workflows/publish.yml
  9. 52
      .github/workflows/sponsors.yml
  10. 4
      .github/workflows/test.yml
  11. 492
      docs/en/data/github_sponsors.yml
  12. 1676
      docs/en/data/people.yml
  13. 5
      docs/en/data/sponsors_badge.yml
  14. 33
      docs/en/docs/fastapi-people.md
  15. 54
      docs/en/docs/release-notes.md
  16. 301
      docs/ja/docs/environment-variables.md
  17. 22
      docs/pt/docs/advanced/settings.md
  18. 6
      docs/pt/docs/tutorial/request-forms.md
  19. 274
      docs/pt/docs/tutorial/security/oauth2-jwt.md
  20. 99
      docs/ru/docs/advanced/async-tests.md
  21. 556
      docs/ru/docs/tutorial/bigger-applications.md
  22. 4
      docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
  23. 2
      fastapi/__init__.py
  24. 11
      pyproject.toml
  25. 2
      requirements-tests.txt
  26. 65
      scripts/notify_translations.py
  27. 401
      scripts/people.py
  28. 221
      scripts/sponsors.py
  29. 21
      tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
  30. 206
      tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py
  31. 213
      tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py
  32. 213
      tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py
  33. 213
      tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
  34. 18
      tests/test_tutorial/test_body_nested_models/test_tutorial009.py
  35. 128
      tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
  36. 19
      tests/test_tutorial/test_body_updates/test_tutorial001.py
  37. 317
      tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
  38. 317
      tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
  39. 29
      tests/test_tutorial/test_cookie_params/test_tutorial001.py
  40. 108
      tests/test_tutorial/test_cookie_params/test_tutorial001_an.py
  41. 114
      tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py
  42. 114
      tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py
  43. 114
      tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
  44. 25
      tests/test_tutorial/test_dependencies/test_tutorial001.py
  45. 183
      tests/test_tutorial/test_dependencies/test_tutorial001_an.py
  46. 191
      tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py
  47. 191
      tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py
  48. 191
      tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
  49. 25
      tests/test_tutorial/test_dependencies/test_tutorial004.py
  50. 162
      tests/test_tutorial/test_dependencies/test_tutorial004_an.py
  51. 170
      tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py
  52. 170
      tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py
  53. 170
      tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
  54. 30
      tests/test_tutorial/test_dependencies/test_tutorial006.py
  55. 149
      tests/test_tutorial/test_dependencies/test_tutorial006_an.py
  56. 161
      tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py
  57. 26
      tests/test_tutorial/test_dependencies/test_tutorial008b.py
  58. 23
      tests/test_tutorial/test_dependencies/test_tutorial008b_an.py
  59. 33
      tests/test_tutorial/test_dependencies/test_tutorial008b_an_py39.py
  60. 36
      tests/test_tutorial/test_dependencies/test_tutorial008c.py
  61. 38
      tests/test_tutorial/test_dependencies/test_tutorial008c_an.py
  62. 44
      tests/test_tutorial/test_dependencies/test_tutorial008c_an_py39.py
  63. 40
      tests/test_tutorial/test_dependencies/test_tutorial008d.py
  64. 41
      tests/test_tutorial/test_dependencies/test_tutorial008d_an.py
  65. 47
      tests/test_tutorial/test_dependencies/test_tutorial008d_an_py39.py
  66. 38
      tests/test_tutorial/test_dependencies/test_tutorial012.py
  67. 250
      tests/test_tutorial/test_dependencies/test_tutorial012_an.py
  68. 266
      tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py
  69. 26
      tests/test_tutorial/test_extra_data_types/test_tutorial001.py
  70. 175
      tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py
  71. 184
      tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py
  72. 184
      tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py
  73. 184
      tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
  74. 25
      tests/test_tutorial/test_extra_models/test_tutorial003.py
  75. 144
      tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
  76. 23
      tests/test_tutorial/test_extra_models/test_tutorial004.py
  77. 67
      tests/test_tutorial/test_extra_models/test_tutorial004_py39.py
  78. 23
      tests/test_tutorial/test_extra_models/test_tutorial005.py
  79. 51
      tests/test_tutorial/test_extra_models/test_tutorial005_py39.py
  80. 24
      tests/test_tutorial/test_header_params/test_tutorial001.py
  81. 102
      tests/test_tutorial/test_header_params/test_tutorial001_an.py
  82. 110
      tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py
  83. 110
      tests/test_tutorial/test_header_params/test_tutorial001_py310.py
  84. 25
      tests/test_tutorial/test_header_params/test_tutorial002.py
  85. 113
      tests/test_tutorial/test_header_params/test_tutorial002_an.py
  86. 121
      tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py
  87. 124
      tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py
  88. 124
      tests/test_tutorial/test_header_params/test_tutorial002_py310.py
  89. 33
      tests/test_tutorial/test_header_params/test_tutorial003.py
  90. 110
      tests/test_tutorial/test_header_params/test_tutorial003_an.py
  91. 118
      tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py
  92. 118
      tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py
  93. 118
      tests/test_tutorial/test_header_params/test_tutorial003_py310.py
  94. 28
      tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
  95. 226
      tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
  96. 226
      tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
  97. 18
      tests/test_tutorial/test_query_params/test_tutorial006.py
  98. 180
      tests/test_tutorial/test_query_params/test_tutorial006_py310.py
  99. 19
      tests/test_tutorial/test_request_form_models/test_tutorial001.py
  100. 232
      tests/test_tutorial/test_request_form_models/test_tutorial001_an.py

7
.github/actions/notify-translations/Dockerfile

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

10
.github/actions/notify-translations/action.yml

@ -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 <tiangolo@gmail.com>"
inputs:
token:
description: 'Token, to read the GitHub API. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: true
runs:
using: 'docker'
image: 'Dockerfile'

7
.github/actions/people/Dockerfile

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

10
.github/actions/people/action.yml

@ -1,10 +0,0 @@
name: "Generate FastAPI People"
description: "Generate the data for the FastAPI People page"
author: "Sebastián Ramírez <tiangolo@gmail.com>"
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'

682
.github/actions/people/app/main.py

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

20
.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' }}

37
.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 }}

2
.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) }}

52
.github/workflows/sponsors.yml

@ -0,0 +1,52 @@
name: FastAPI People Sponsors
on:
schedule:
- cron: "0 6 1 * *"
workflow_dispatch:
inputs:
debug_enabled:
description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)"
required: false
default: "false"
env:
UV_SYSTEM_PYTHON: 1
jobs:
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
- 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
- name: FastAPI People Sponsors
run: python ./scripts/sponsors.py
env:
SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}

4
.github/workflows/test.yml

@ -81,6 +81,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

492
docs/en/data/github_sponsors.yml

@ -2,57 +2,60 @@ sponsors:
- - login: bump-sh
avatarUrl: https://avatars.githubusercontent.com/u/33217836?v=4
url: https://github.com/bump-sh
- login: porter-dev
avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4
url: https://github.com/porter-dev
- login: Nixtla
avatarUrl: https://avatars.githubusercontent.com/u/79945230?v=4
url: https://github.com/Nixtla
- login: andrew-propelauth
avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=1188c27cb744bbec36447a2cfd4453126b2ddb5c&v=4
url: https://github.com/andrew-propelauth
- login: liblaber
avatarUrl: https://avatars.githubusercontent.com/u/100821118?v=4
url: https://github.com/liblaber
- login: zanfaruqui
avatarUrl: https://avatars.githubusercontent.com/u/104461687?v=4
url: https://github.com/zanfaruqui
- login: Alek99
avatarUrl: https://avatars.githubusercontent.com/u/38776361?u=bd6c163fe787c2de1a26c881598e54b67e2482dd&v=4
url: https://github.com/Alek99
- login: cryptapi
avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
url: https://github.com/cryptapi
- login: Kong
avatarUrl: https://avatars.githubusercontent.com/u/962416?v=4
url: https://github.com/Kong
- login: codacy
avatarUrl: https://avatars.githubusercontent.com/u/1834093?v=4
url: https://github.com/codacy
- login: blockbee-io
avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4
url: https://github.com/blockbee-io
- login: zuplo
avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4
url: https://github.com/zuplo
- login: render-sponsorships
avatarUrl: https://avatars.githubusercontent.com/u/189296666?v=4
url: https://github.com/render-sponsorships
- login: porter-dev
avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4
url: https://github.com/porter-dev
- login: scalar
avatarUrl: https://avatars.githubusercontent.com/u/301879?v=4
url: https://github.com/scalar
- - login: ObliviousAI
avatarUrl: https://avatars.githubusercontent.com/u/65656077?v=4
url: https://github.com/ObliviousAI
- - login: databento
avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
url: https://github.com/databento
- login: svix
- - login: svix
avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
url: https://github.com/svix
- login: deepset-ai
avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4
url: https://github.com/deepset-ai
- login: mikeckennedy
avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=ce6165b799ea3164cb6f5ff54ea08042057442af&v=4
url: https://github.com/mikeckennedy
- login: ndimares
avatarUrl: https://avatars.githubusercontent.com/u/6267663?u=cfb27efde7a7212be8142abb6c058a1aeadb41b1&v=4
url: https://github.com/ndimares
- - login: takashi-yoneya
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
url: https://github.com/takashi-yoneya
- login: stainless-api
avatarUrl: https://avatars.githubusercontent.com/u/88061651?v=4
url: https://github.com/stainless-api
- login: speakeasy-api
avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4
url: https://github.com/speakeasy-api
- login: databento
avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
url: https://github.com/databento
- - login: mercedes-benz
avatarUrl: https://avatars.githubusercontent.com/u/34240465?v=4
url: https://github.com/mercedes-benz
- login: xoflare
avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4
url: https://github.com/xoflare
- login: marvin-robot
avatarUrl: https://avatars.githubusercontent.com/u/41086007?u=b9fcab402d0cd0aec738b6574fe60855cb0cd36d&v=4
url: https://github.com/marvin-robot
- login: Ponte-Energy-Partners
avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4
url: https://github.com/Ponte-Energy-Partners
- login: BoostryJP
avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
url: https://github.com/BoostryJP
@ -62,42 +65,63 @@ sponsors:
- - login: Trivie
avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4
url: https://github.com/Trivie
- - login: americanair
avatarUrl: https://avatars.githubusercontent.com/u/12281813?v=4
url: https://github.com/americanair
- - login: takashi-yoneya
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
url: https://github.com/takashi-yoneya
- - login: mainframeindustries
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
url: https://github.com/mainframeindustries
- login: CanoaPBC
avatarUrl: https://avatars.githubusercontent.com/u/64223768?v=4
url: https://github.com/CanoaPBC
- login: mainframeindustries
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
url: https://github.com/mainframeindustries
- login: mangualero
avatarUrl: https://avatars.githubusercontent.com/u/3422968?u=c59272d7b5a912d6126fd6c6f17db71e20f506eb&v=4
url: https://github.com/mangualero
- login: birkjernstrom
avatarUrl: https://avatars.githubusercontent.com/u/281715?u=4be14b43f76b4bd497b1941309bb390250b405e6&v=4
url: https://github.com/birkjernstrom
- login: yasyf
avatarUrl: https://avatars.githubusercontent.com/u/709645?u=f36736b3c6a85f578886ecc42a740e7b436e7a01&v=4
url: https://github.com/yasyf
- - login: genzou9201
avatarUrl: https://avatars.githubusercontent.com/u/42960762?u=1ca6c18c59e8b327ae584c545b72de31ebc05275&v=4
url: https://github.com/genzou9201
- - login: primer-io
avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
url: https://github.com/primer-io
- login: povilasb
avatarUrl: https://avatars.githubusercontent.com/u/1213442?u=b11f58ed6ceea6e8297c9b310030478ebdac894d&v=4
url: https://github.com/povilasb
- - login: jhundman
avatarUrl: https://avatars.githubusercontent.com/u/24263908?v=4
url: https://github.com/jhundman
- login: upciti
- - login: upciti
avatarUrl: https://avatars.githubusercontent.com/u/43346262?v=4
url: https://github.com/upciti
- login: freddiev4
avatarUrl: https://avatars.githubusercontent.com/u/8339018?u=1aad5b4f5a04cb750852b843d5e1d8f4ce339c2e&v=4
url: https://github.com/freddiev4
- - login: samuelcolvin
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4
url: https://github.com/samuelcolvin
- login: Kludex
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: vincentkoc
avatarUrl: https://avatars.githubusercontent.com/u/25068?u=cbf098fc04c0473523d373b0dd2145b4ec99ef93&v=4
url: https://github.com/vincentkoc
- login: ProteinQure
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
url: https://github.com/ProteinQure
- login: ddilidili
avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4
url: https://github.com/ddilidili
- login: otosky
avatarUrl: https://avatars.githubusercontent.com/u/42260747?u=69d089387c743d89427aa4ad8740cfb34045a9e0&v=4
url: https://github.com/otosky
- login: mjohnsey
avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
url: https://github.com/mjohnsey
- login: ashi-agrawal
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
url: https://github.com/ashi-agrawal
- login: sepsi77
avatarUrl: https://avatars.githubusercontent.com/u/18682303?v=4
url: https://github.com/sepsi77
- login: RaamEEIL
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
url: https://github.com/RaamEEIL
- login: jhundman
avatarUrl: https://avatars.githubusercontent.com/u/24263908?v=4
url: https://github.com/jhundman
- login: b-rad-c
avatarUrl: https://avatars.githubusercontent.com/u/25362581?u=5bb10629f4015b62bec1f9a366675d5085551af9&v=4
url: https://github.com/b-rad-c
@ -105,7 +129,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4
url: https://github.com/ehaca
- login: raphaellaude
avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=9ae4b158c0d2cb29ebd46df6b6edb7de08a67566&v=4
avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=28faad3e62250ef91a0c3c5d0faba39592d9ab39&v=4
url: https://github.com/raphaellaude
- login: timlrx
avatarUrl: https://avatars.githubusercontent.com/u/28362229?u=9a745ca31372ee324af682715ae88ce8522f9094&v=4
@ -116,78 +140,51 @@ sponsors:
- login: ygorpontelo
avatarUrl: https://avatars.githubusercontent.com/u/32963605?u=35f7103f9c4c4c2589ae5737ee882e9375ef072e&v=4
url: https://github.com/ygorpontelo
- login: ProteinQure
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
url: https://github.com/ProteinQure
- login: catherinenelson1
avatarUrl: https://avatars.githubusercontent.com/u/11951946?u=e714b957185b8cf3d301cced7fc3ad2842122c6a&v=4
url: https://github.com/catherinenelson1
- login: jsoques
avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
url: https://github.com/jsoques
- login: joeds13
avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4
url: https://github.com/joeds13
- login: dannywade
avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
url: https://github.com/dannywade
- login: khadrawy
avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
url: https://github.com/khadrawy
- login: mjohnsey
avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
url: https://github.com/mjohnsey
- login: ashi-agrawal
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
url: https://github.com/ashi-agrawal
- login: sepsi77
avatarUrl: https://avatars.githubusercontent.com/u/18682303?v=4
url: https://github.com/sepsi77
- login: wedwardbeck
avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
url: https://github.com/wedwardbeck
- login: RaamEEIL
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
url: https://github.com/RaamEEIL
- login: anthonycepeda
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
url: https://github.com/anthonycepeda
- login: patricioperezv
avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
url: https://github.com/patricioperezv
- login: chickenandstats
avatarUrl: https://avatars.githubusercontent.com/u/79477966?v=4
url: https://github.com/chickenandstats
- login: kaoru0310
avatarUrl: https://avatars.githubusercontent.com/u/80977929?u=1b61d10142b490e56af932ddf08a390fae8ee94f&v=4
url: https://github.com/kaoru0310
- login: DelfinaCare
avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4
url: https://github.com/DelfinaCare
- login: Eruditis
- login: Karine-Bauch
avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4
url: https://github.com/Karine-Bauch
- login: eruditis
avatarUrl: https://avatars.githubusercontent.com/u/95244703?v=4
url: https://github.com/Eruditis
url: https://github.com/eruditis
- login: jugeeem
avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=ae590d79c38ac79c91b9c5caa6887d061e865a3d&v=4
url: https://github.com/jugeeem
- login: apitally
avatarUrl: https://avatars.githubusercontent.com/u/138365043?v=4
url: https://github.com/apitally
- login: logic-automation
avatarUrl: https://avatars.githubusercontent.com/u/144732884?v=4
url: https://github.com/logic-automation
- login: ddilidili
avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4
url: https://github.com/ddilidili
- login: Torqsight-Labs
avatarUrl: https://avatars.githubusercontent.com/u/169598176?v=4
url: https://github.com/Torqsight-Labs
- login: ramonalmeidam
avatarUrl: https://avatars.githubusercontent.com/u/45269580?u=3358750b3a5854d7c3ed77aaca7dd20a0f529d32&v=4
url: https://github.com/ramonalmeidam
- login: roboflow
avatarUrl: https://avatars.githubusercontent.com/u/53104118?v=4
url: https://github.com/roboflow
- login: dudikbender
avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=3a57542938ebfd57579a0111db2b297e606d9681&v=4
url: https://github.com/dudikbender
- login: prodhype
avatarUrl: https://avatars.githubusercontent.com/u/60444672?u=3f278cff25ea37ead487d7861d4a984795de819e&v=4
url: https://github.com/prodhype
- login: patsatsia
avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4
url: https://github.com/patsatsia
- login: anthonycepeda
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
url: https://github.com/anthonycepeda
- login: patricioperezv
avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
url: https://github.com/patricioperezv
- login: mintuhouse
avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
url: https://github.com/mintuhouse
- login: tcsmith
avatarUrl: https://avatars.githubusercontent.com/u/989034?u=7d8d741552b3279e8f4d3878679823a705a46f8f&v=4
url: https://github.com/tcsmith
@ -200,9 +197,6 @@ sponsors:
- login: knallgelb
avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4
url: https://github.com/knallgelb
- login: johannquerne
avatarUrl: https://avatars.githubusercontent.com/u/2736484?u=9b3381546a25679913a2b08110e4373c98840821&v=4
url: https://github.com/johannquerne
- login: Shark009
avatarUrl: https://avatars.githubusercontent.com/u/3163309?u=0c6f4091b0eda05c44c390466199826e6dc6e431&v=4
url: https://github.com/Shark009
@ -215,15 +209,18 @@ sponsors:
- login: kennywakeland
avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4
url: https://github.com/kennywakeland
- login: simw
avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
url: https://github.com/simw
- login: koconder
avatarUrl: https://avatars.githubusercontent.com/u/25068?u=582657b23622aaa3dfe68bd028a780f272f456fa&v=4
url: https://github.com/koconder
- login: aacayaco
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
url: https://github.com/aacayaco
- login: anomaly
avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
url: https://github.com/anomaly
- login: jstanden
avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4
url: https://github.com/jstanden
- login: paulcwatts
avatarUrl: https://avatars.githubusercontent.com/u/150269?u=1819e145d573b44f0ad74b87206d21cd60331d4e&v=4
url: https://github.com/paulcwatts
- login: andreaso
avatarUrl: https://avatars.githubusercontent.com/u/285964?u=837265cc7562c0685f25b2d81cd9de0434fe107c&v=4
url: https://github.com/andreaso
@ -239,36 +236,36 @@ sponsors:
- login: wshayes
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
- login: gaetanBloch
avatarUrl: https://avatars.githubusercontent.com/u/583199?u=50c49e83d6b4feb78a091901ea02ead1462f442b&v=4
url: https://github.com/gaetanBloch
- login: koxudaxi
avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
url: https://github.com/koxudaxi
- login: falkben
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
url: https://github.com/falkben
- login: mintuhouse
avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
url: https://github.com/mintuhouse
- login: Rehket
avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
url: https://github.com/Rehket
- login: hiancdtrsnm
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
url: https://github.com/hiancdtrsnm
- login: TrevorBenson
avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=afdd1766fdb79e04e59094cc6a54cd011ee7f686&v=4
avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=dccbea3327a57750923333d8ebf1a0b3f1948949&v=4
url: https://github.com/TrevorBenson
- login: wdwinslow
avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
url: https://github.com/wdwinslow
- login: aacayaco
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
url: https://github.com/aacayaco
- login: anomaly
avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
url: https://github.com/anomaly
- login: jgreys
avatarUrl: https://avatars.githubusercontent.com/u/4136890?u=096820d1ef89877d57d0f68e669ead8b0fde84df&v=4
url: https://github.com/jgreys
- login: catherinenelson1
avatarUrl: https://avatars.githubusercontent.com/u/11951946?u=fe11bc35d36b6038cd46a946e4e46ef8aa5688ab&v=4
url: https://github.com/catherinenelson1
- login: jsoques
avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
url: https://github.com/jsoques
- login: joeds13
avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4
url: https://github.com/joeds13
- login: dannywade
avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
url: https://github.com/dannywade
- login: khadrawy
avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
url: https://github.com/khadrawy
- login: Ryandaydev
avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=48f68868db8886fce31a1d802c1003914c6cd7c6&v=4
url: https://github.com/Ryandaydev
@ -290,108 +287,81 @@ sponsors:
- login: FernandoCelmer
avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=d29fff3fd862fda4ca752079f13f32e84c762ea4&v=4
url: https://github.com/FernandoCelmer
- - login: getsentry
avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4
url: https://github.com/getsentry
- login: simw
avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
url: https://github.com/simw
- login: Rehket
avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
url: https://github.com/Rehket
- login: hiancdtrsnm
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
url: https://github.com/hiancdtrsnm
- - login: pawamoy
avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
url: https://github.com/pawamoy
- login: SebTota
avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4
url: https://github.com/SebTota
- login: nisutec
avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4
url: https://github.com/nisutec
- login: hoenie-ams
avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4
url: https://github.com/hoenie-ams
- login: joerambo
avatarUrl: https://avatars.githubusercontent.com/u/26282974?v=4
url: https://github.com/joerambo
- login: rlnchow
avatarUrl: https://avatars.githubusercontent.com/u/28018479?u=a93ca9cf1422b9ece155784a72d5f2fdbce7adff&v=4
url: https://github.com/rlnchow
- login: engineerjoe440
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
url: https://github.com/engineerjoe440
- login: bnkc
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4
url: https://github.com/bnkc
- login: DevOpsKev
avatarUrl: https://avatars.githubusercontent.com/u/36336550?u=6ccd5978fdaab06f37e22f2a14a7439341df7f67&v=4
url: https://github.com/DevOpsKev
- login: petercool
avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=81c525232bb35780945a68e88afd96bb2cdad9c4&v=4
url: https://github.com/petercool
- login: JimFawkes
avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4
url: https://github.com/JimFawkes
- login: artempronevskiy
avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4
url: https://github.com/artempronevskiy
- login: TheR1D
avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
url: https://github.com/TheR1D
- login: joshuatz
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
url: https://github.com/joshuatz
- login: jangia
avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4
url: https://github.com/jangia
- login: jackleeio
avatarUrl: https://avatars.githubusercontent.com/u/20477587?u=c5184dab6d021733d10c8f975b20e391856303d6&v=4
url: https://github.com/jackleeio
- login: shuheng-liu
avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
url: https://github.com/shuheng-liu
- login: pers0n4
avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=f211a13a7b572cbbd7779b9c8d8cb428cc7ba07e&v=4
url: https://github.com/pers0n4
- login: curegit
avatarUrl: https://avatars.githubusercontent.com/u/37978051?u=1733c322079118c0cdc573c03d92813f50a9faec&v=4
url: https://github.com/curegit
- login: fernandosmither
avatarUrl: https://avatars.githubusercontent.com/u/66154723?u=f79753eb207d01cca5bbb91ac62db6123e7622d1&v=4
url: https://github.com/fernandosmither
- login: PunRabbit
avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
url: https://github.com/PunRabbit
- login: PelicanQ
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
url: https://github.com/PelicanQ
- login: tahmarrrr23
avatarUrl: https://avatars.githubusercontent.com/u/138208610?u=465a46b0ff72a74252d3e3a71ac7d2f1919cda28&v=4
url: https://github.com/tahmarrrr23
- login: zk-Call
avatarUrl: https://avatars.githubusercontent.com/u/147117264?v=4
url: https://github.com/zk-Call
- login: kristiangronberg
avatarUrl: https://avatars.githubusercontent.com/u/42678548?v=4
url: https://github.com/kristiangronberg
- login: leonardo-holguin
avatarUrl: https://avatars.githubusercontent.com/u/43093055?u=b59013d52fb6c4e0954aaaabc0882bd844985b38&v=4
url: https://github.com/leonardo-holguin
- login: arrrrrmin
avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=36a3880a6eb29309c19e6cadbb173bafbe91deb1&v=4
url: https://github.com/arrrrrmin
- login: siavashyj
avatarUrl: https://avatars.githubusercontent.com/u/43583410?u=562005ddc7901cd27a1219a118a2363817b14977&v=4
url: https://github.com/siavashyj
- login: mobyw
avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4
url: https://github.com/mobyw
- login: ArtyomVancyan
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
url: https://github.com/ArtyomVancyan
- login: harol97
avatarUrl: https://avatars.githubusercontent.com/u/49042862?u=2b18e115ab73f5f09a280be2850f93c58a12e3d2&v=4
url: https://github.com/harol97
- login: TheR1D
avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
url: https://github.com/TheR1D
- login: joshuatz
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
url: https://github.com/joshuatz
- login: SebTota
avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4
url: https://github.com/SebTota
- login: nisutec
avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4
url: https://github.com/nisutec
- login: hoenie-ams
avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4
url: https://github.com/hoenie-ams
- login: joerambo
avatarUrl: https://avatars.githubusercontent.com/u/26282974?v=4
url: https://github.com/joerambo
- login: rlnchow
avatarUrl: https://avatars.githubusercontent.com/u/28018479?u=a93ca9cf1422b9ece155784a72d5f2fdbce7adff&v=4
url: https://github.com/rlnchow
- login: dvlpjrs
avatarUrl: https://avatars.githubusercontent.com/u/32254642?u=fbd6ad0324d4f1eb6231cf775be1c7bd4404e961&v=4
url: https://github.com/dvlpjrs
- login: caviri
avatarUrl: https://avatars.githubusercontent.com/u/45425937?u=4e14bd64282bad8f385eafbdb004b5a279366d6e&v=4
url: https://github.com/caviri
- login: hgalytoby
avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=62c7ff3519858423579676cd0efbd7e3f1ffe63a&v=4
url: https://github.com/hgalytoby
- login: conservative-dude
avatarUrl: https://avatars.githubusercontent.com/u/55538308?u=f250c44942ea6e73a6bd90739b381c470c192c11&v=4
url: https://github.com/conservative-dude
- login: Joaopcamposs
avatarUrl: https://avatars.githubusercontent.com/u/57376574?u=699d5ba5ee66af1d089df6b5e532b97169e73650&v=4
url: https://github.com/Joaopcamposs
- login: CR1337
avatarUrl: https://avatars.githubusercontent.com/u/62649536?u=57a6aab10d2421a497306da8bcded01b826c54ae&v=4
url: https://github.com/CR1337
- login: PunRabbit
avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
url: https://github.com/PunRabbit
- login: PelicanQ
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
url: https://github.com/PelicanQ
- login: tochikuji
avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
url: https://github.com/tochikuji
- login: browniebroke
avatarUrl: https://avatars.githubusercontent.com/u/861044?u=5abfca5588f3e906b31583d7ee62f6de4b68aa24&v=4
url: https://github.com/browniebroke
@ -407,9 +377,12 @@ sponsors:
- login: leobiscassi
avatarUrl: https://avatars.githubusercontent.com/u/1977418?u=f9f82445a847ab479bd7223debd677fcac6c49a0&v=4
url: https://github.com/leobiscassi
- login: cbonoz
avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
url: https://github.com/cbonoz
- login: Alisa-lisa
avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
url: https://github.com/Alisa-lisa
- login: Graeme22
avatarUrl: https://avatars.githubusercontent.com/u/4185684?u=498182a42300d7bcd4de1215190cb17eb501136c&v=4
url: https://github.com/Graeme22
- login: ddanier
avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4
url: https://github.com/ddanier
@ -419,33 +392,15 @@ sponsors:
- login: slafs
avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
url: https://github.com/slafs
- login: adamghill
avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4
url: https://github.com/adamghill
- login: ceb10n
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
- login: eteq
avatarUrl: https://avatars.githubusercontent.com/u/346587?v=4
url: https://github.com/eteq
- login: dmig
avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4
url: https://github.com/dmig
- login: securancy
avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4
url: https://github.com/securancy
- login: tochikuji
avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
url: https://github.com/tochikuji
- login: KentShikama
avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
url: https://github.com/KentShikama
- login: katnoria
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
url: https://github.com/katnoria
- login: harsh183
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
url: https://github.com/harsh183
- login: hcristea
avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4
url: https://github.com/hcristea
- login: moonape1226
avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4
url: https://github.com/moonape1226
@ -453,7 +408,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/9369632?u=8c988f1b008a3f601385a3616f9327820f66e3a5&v=4
url: https://github.com/msehnout
- login: xncbf
avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=2ef1ede118a72c170805f50b9ad07341fd16a354&v=4
avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4
url: https://github.com/xncbf
- login: DMantis
avatarUrl: https://avatars.githubusercontent.com/u/9536869?v=4
@ -464,9 +419,6 @@ sponsors:
- login: supdann
avatarUrl: https://avatars.githubusercontent.com/u/9986994?u=9671810f4ae9504c063227fee34fd47567ff6954&v=4
url: https://github.com/supdann
- login: satwikkansal
avatarUrl: https://avatars.githubusercontent.com/u/10217535?u=b12d6ef74ea297de9e46da6933b1a5b7ba9e6a61&v=4
url: https://github.com/satwikkansal
- login: mntolia
avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4
url: https://github.com/mntolia
@ -479,17 +431,14 @@ sponsors:
- login: Zuzah
avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4
url: https://github.com/Zuzah
- login: Alisa-lisa
avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
url: https://github.com/Alisa-lisa
- login: Graeme22
avatarUrl: https://avatars.githubusercontent.com/u/4185684?u=498182a42300d7bcd4de1215190cb17eb501136c&v=4
url: https://github.com/Graeme22
- login: artempronevskiy
avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4
url: https://github.com/artempronevskiy
- login: danielunderwood
avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
url: https://github.com/danielunderwood
- login: rangulvers
avatarUrl: https://avatars.githubusercontent.com/u/5235430?v=4
avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4
url: https://github.com/rangulvers
- login: sdevkota
avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4
@ -500,33 +449,42 @@ sponsors:
- login: Baghdady92
avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4
url: https://github.com/Baghdady92
- login: jakeecolution
avatarUrl: https://avatars.githubusercontent.com/u/5884696?u=4a7c7883fb064b593b50cb6697b54687e6f7aafe&v=4
url: https://github.com/jakeecolution
- login: stephane-rbn
avatarUrl: https://avatars.githubusercontent.com/u/5939522?u=eb7ffe768fa3bcbcd04de14fe4a47444cc00ec4c&v=4
url: https://github.com/stephane-rbn
- - login: danburonline
avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=94935cccfbec58083ab1e535212d54f1bf2c978a&v=4
url: https://github.com/danburonline
- login: AliYmn
avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=0de5a262e8b4dc0a08d065f30f7a39941e246530&v=4
url: https://github.com/AliYmn
- login: sadikkuzu
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
url: https://github.com/sadikkuzu
- login: tran-hai-long
avatarUrl: https://avatars.githubusercontent.com/u/119793901?u=3b173a845dcf099b275bdc9713a69cbbc36040ce&v=4
url: https://github.com/tran-hai-long
- login: KentShikama
avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
url: https://github.com/KentShikama
- login: katnoria
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
url: https://github.com/katnoria
- login: harsh183
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
url: https://github.com/harsh183
- login: hcristea
avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4
url: https://github.com/hcristea
- - login: larsyngvelundin
avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4
url: https://github.com/larsyngvelundin
- login: andrecorumba
avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4
url: https://github.com/andrecorumba
- login: rwxd
avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4
url: https://github.com/rwxd
- login: sadikkuzu
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
url: https://github.com/sadikkuzu
- login: Olegt0rr
avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4
url: https://github.com/Olegt0rr
- login: FabulousCodingFox
avatarUrl: https://avatars.githubusercontent.com/u/78906517?u=924a27cbee3db7e0ece5cc1509921402e1445e74&v=4
url: https://github.com/FabulousCodingFox
- login: anqorithm
avatarUrl: https://avatars.githubusercontent.com/u/61029571?u=468256fa4e2d9ce2870b608299724bebb7a33f18&v=4
url: https://github.com/anqorithm
- login: ssbarnea
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c2efbf6fea2737e21dfc6b1113c4edc9644e9eaa&v=4
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c7bd9ddf127785286fc939dd18cb02db0a453bce&v=4
url: https://github.com/ssbarnea
- login: yuawn
avatarUrl: https://avatars.githubusercontent.com/u/5111198?u=5315576f3fe1a70fd2d0f02181588f4eea5d353d&v=4
url: https://github.com/yuawn
- login: dongzhenye
avatarUrl: https://avatars.githubusercontent.com/u/5765843?u=fe420c9a4c41e5b060faaf44029f5485616b470d&v=4
url: https://github.com/dongzhenye
- login: andreagrandi
avatarUrl: https://avatars.githubusercontent.com/u/636391?u=13d90cb8ec313593a5b71fbd4e33b78d6da736f5&v=4
url: https://github.com/andreagrandi

1676
docs/en/data/people.yml

File diff suppressed because it is too large

5
docs/en/data/sponsors_badge.yml

@ -29,7 +29,12 @@ logins:
- andrew-propelauth
- svix
- zuplo-oss
- zuplo
- Kong
- speakeasy-api
- jess-render
- blockbee-io
- liblaber
- render-sponsorships
- renderinc
- stainless-api

33
docs/en/docs/fastapi-people.md

@ -47,9 +47,11 @@ This is the current list of team members. 😎
They have different levels of involvement and permissions, they can perform [repository management tasks](./management-tasks.md){.internal-link target=_blank} and together we [manage the FastAPI repository](./management.md){.internal-link target=_blank}.
<div class="user-list user-list-center">
{% for user in members["members"] %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatar_url }}"/></div><div class="title">@{{ user.login }}</div></a></div>
{% endfor %}
</div>
@ -83,9 +85,15 @@ You can see the **FastAPI Experts** for:
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last month. 🤓
<div class="user-list user-list-center">
{% for user in people.last_month_experts[:10] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
@ -95,9 +103,15 @@ These are the users that have been [helping others the most with questions in Gi
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 3 months. 😎
<div class="user-list user-list-center">
{% for user in people.three_months_experts[:10] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
@ -107,9 +121,15 @@ These are the users that have been [helping others the most with questions in Gi
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 6 months. 🧐
<div class="user-list user-list-center">
{% for user in people.six_months_experts[:10] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
@ -119,9 +139,15 @@ These are the users that have been [helping others the most with questions in Gi
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last year. 🧑‍🔬
<div class="user-list user-list-center">
{% for user in people.one_year_experts[:20] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
@ -133,9 +159,15 @@ Here are the all time **FastAPI Experts**. 🤓🤯
These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} through *all time*. 🧙
<div class="user-list user-list-center">
{% for user in people.experts[:50] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
@ -149,6 +181,7 @@ These users have [created the most Pull Requests](help-fastapi.md#create-a-pull-
They have contributed source code, documentation, etc. 📦
<div class="user-list user-list-center">
{% for user in (contributors.values() | list)[:50] %}
{% if user.login not in skip_users %}

54
docs/en/docs/release-notes.md

@ -7,8 +7,54 @@ hide:
## Latest Changes
### Docs
* 👥 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
* ⬆️ 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).
* ✅ Simplify tests for security. PR [#13200](https://github.com/fastapi/fastapi/pull/13200) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for schema_extra_example. PR [#13197](https://github.com/fastapi/fastapi/pull/13197) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for request_model. PR [#13195](https://github.com/fastapi/fastapi/pull/13195) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for request_forms_and_files. PR [#13185](https://github.com/fastapi/fastapi/pull/13185) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for request_forms. PR [#13184](https://github.com/fastapi/fastapi/pull/13184) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for path_query_params. PR [#13181](https://github.com/fastapi/fastapi/pull/13181) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for path_operation_configurations. PR [#13180](https://github.com/fastapi/fastapi/pull/13180) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for header_params. PR [#13179](https://github.com/fastapi/fastapi/pull/13179) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for extra_models. PR [#13178](https://github.com/fastapi/fastapi/pull/13178) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for extra_data_types. PR [#13177](https://github.com/fastapi/fastapi/pull/13177) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for cookie_params. PR [#13176](https://github.com/fastapi/fastapi/pull/13176) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for dependencies. PR [#13174](https://github.com/fastapi/fastapi/pull/13174) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_updates. PR [#13172](https://github.com/fastapi/fastapi/pull/13172) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_nested_models. PR [#13171](https://github.com/fastapi/fastapi/pull/13171) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_multiple_params. PR [#13170](https://github.com/fastapi/fastapi/pull/13170) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_fields. PR [#13169](https://github.com/fastapi/fastapi/pull/13169) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body. PR [#13168](https://github.com/fastapi/fastapi/pull/13168) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for bigger_applications. PR [#13167](https://github.com/fastapi/fastapi/pull/13167) by [@alejsdev](https://github.com/alejsdev).
@ -31,6 +77,9 @@ 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).
* 🌐 Add Indonesian translation for `docs/id/docs/tutorial/static-files.md`. PR [#13092](https://github.com/fastapi/fastapi/pull/13092) by [@guspan-tanadi](https://github.com/guspan-tanadi).
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/security/get-current-user.md`. PR [#13188](https://github.com/fastapi/fastapi/pull/13188) by [@ceb10n](https://github.com/ceb10n).
@ -83,6 +132,11 @@ 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).
* 👷 Add independent CI automation for FastAPI People - Sponsors. PR [#13221](https://github.com/fastapi/fastapi/pull/13221) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add retries to Smokeshow. PR [#13151](https://github.com/fastapi/fastapi/pull/13151) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update Speakeasy sponsor graphic. PR [#13147](https://github.com/fastapi/fastapi/pull/13147) by [@chailandau](https://github.com/chailandau).
* 👥 Update FastAPI GitHub topic repositories. PR [#13146](https://github.com/fastapi/fastapi/pull/13146) by [@tiangolo](https://github.com/tiangolo).

301
docs/ja/docs/environment-variables.md

@ -0,0 +1,301 @@
# 環境変数
/// tip
もし、「環境変数」とは何か、それをどう使うかを既に知っている場合は、このセクションをスキップして構いません。
///
環境変数(**env var**とも呼ばれる)はPythonコードの**外側**、つまり**OS**に存在する変数で、Pythonから読み取ることができます。(他のプログラムでも同様に読み取れます。)
環境変数は、アプリケーションの**設定**の管理や、Pythonの**インストール**などに役立ちます。
## 環境変数の作成と使用
環境変数は**シェル(ターミナル)**内で**作成**して使用でき、それらにPythonは不要です。
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```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
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```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
```
</div>
////
## Pythonで環境変数を読み取る
環境変数をPythonの**外側**、ターミナル(や他の方法)で作成し、**Python内で読み取る**こともできます。
例えば、以下のような`main.py`ファイルを用意します:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> の第2引数は、デフォルトで返される値を指定します。
この引数を省略するとデフォルト値として`None`が返されますが、ここではデフォルト値として`"World"`を指定しています。
///
次に、このPythonプログラムを呼び出します。
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```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
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```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
```
</div>
////
環境変数はコードの外側で設定し、内側から読み取ることができるので、他のファイルと一緒に(`git`に)保存する必要がありません。そのため、環境変数をコンフィグレーションや**設定**に使用することが一般的です。
また、**特定のプログラムの呼び出し**のための環境変数を、そのプログラムのみ、その実行中に限定して利用できるよう作成できます。
そのためには、プログラム起動コマンドと同じコマンドライン上の、起動コマンド直前で環境変数を作成してください。
<div class="termy">
```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
```
</div>
/// tip
詳しくは <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a> を参照してください。
///
## 型とバリデーション
環境変数は**テキスト文字列**のみを扱うことができます。これは、環境変数が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プログラムを見つけ、使用します。
///
つまり、ターミナルで以下のコマンドを入力すると:
<div class="termy">
``` console
$ python
```
</div>
/// tab | Linux, macOS
OSは`/opt/custompython/bin`にある`python`プログラムを**見つけ**て実行します。
これは、次のコマンドを入力した場合とほとんど同等です:
<div class="termy">
```console
$ /opt/custompython/bin/python
```
</div>
///
/// tab | Windows
OSは`C:\opt\custompython\bin\python`にある`python`プログラムを**見つけ**て実行します。
これは、次のコマンドを入力した場合とほとんど同等です:
<div class="termy">
```console
$ C:\opt\custompython\bin\python
```
</div>
///
この情報は、[Virtual Environments](virtual-environments.md) について学ぶ際にも役立ちます。
## まとめ
これで、**環境変数**とは何か、Pythonでどのように使用するかについて、基本的な理解が得られたはずです。
環境変数についての詳細は、<a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia: Environment Variable</a> を参照してください。
環境変数の用途や適用方法が最初は直感的ではないかもしれませんが、開発中のさまざまなシナリオで繰り返し登場します。そのため、基本を知っておくことが重要です。
たとえば、この情報は次のセクションで扱う[Virtual Environments](virtual-environments.md)にも関連します。

22
docs/pt/docs/advanced/settings.md

@ -8,7 +8,7 @@ Por isso é comum prover essas configurações como variáveis de ambiente que s
## Variáveis de Ambiente
/// dica
/// tip | Dica
Se você já sabe o que são variáveis de ambiente e como utilizá-las, sinta-se livre para avançar para o próximo tópico.
@ -67,7 +67,7 @@ name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// dica
/// tip | Dica
O segundo parâmetro em <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> é o valor padrão para o retorno.
@ -124,7 +124,7 @@ Hello World from Python
</div>
/// dica
/// tip | Dica
Você pode ler mais sobre isso em: <a href="https://12factor.net/pt_br/config" class="external-link" target="_blank">The Twelve-Factor App: Configurações</a>.
@ -196,7 +196,7 @@ Na versão 1 do Pydantic você importaria `BaseSettings` diretamente do módulo
////
/// dica
/// tip | Dica
Se você quiser algo pronto para copiar e colar na sua aplicação, não use esse exemplo, mas sim o exemplo abaixo.
@ -226,7 +226,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p
</div>
/// dica
/// tip | Dica
Para definir múltiplas variáveis de ambiente para um único comando basta separá-las utilizando espaços, e incluir todas elas antes do comando.
@ -250,7 +250,7 @@ E utilizar essa configuração em `main.py`:
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
/// dica
/// tip | Dica
Você também precisa incluir um arquivo `__init__.py` como visto em [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=\_blank}.
@ -276,7 +276,7 @@ Agora criamos a dependência que retorna um novo objeto `config.Settings()`.
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// dica
/// tip | Dica
Vamos discutir sobre `@lru_cache` logo mais.
@ -304,7 +304,7 @@ Se você tiver muitas configurações que variem bastante, talvez em ambientes d
Essa prática é tão comum que possui um nome, essas variáveis de ambiente normalmente são colocadas em um arquivo `.env`, e esse arquivo é chamado de "dotenv".
/// dica
/// tip | Dica
Um arquivo iniciando com um ponto final (`.`) é um arquivo oculto em sistemas baseados em Unix, como Linux e MacOS.
@ -314,7 +314,7 @@ Mas um arquivo dotenv não precisa ter esse nome exato.
Pydantic suporta a leitura desses tipos de arquivos utilizando uma biblioteca externa. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>.
/// dica
/// tip | Dica
Para que isso funcione você precisa executar `pip install python-dotenv`.
@ -337,7 +337,7 @@ E então adicionar o seguinte código em `config.py`:
{* ../../docs_src/settings/app03_an/config.py hl[9] *}
/// dica
/// tip | Dica
O atributo `model_config` é usado apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
@ -349,7 +349,7 @@ O atributo `model_config` é usado apenas para configuração do Pydantic. Você
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
/// dica
/// tip | Dica
A classe `Config` é usada apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.

6
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 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
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
```
///

274
docs/pt/docs/tutorial/security/oauth2-jwt.md

@ -0,0 +1,274 @@
# OAuth2 com Senha (e hashing), Bearer com tokens JWT
Agora que temos todo o fluxo de segurança, vamos tornar a aplicação realmente segura, usando tokens <abbr title="JSON Web Tokens">JWT</abbr> e hashing de senhas seguras.
Este código é algo que você pode realmente usar na sua aplicação, salvar os hashes das senhas no seu banco de dados, etc.
Vamos começar de onde paramos no capítulo anterior e incrementá-lo.
## Sobre o JWT
JWT significa "JSON Web Tokens".
É um padrão para codificar um objeto JSON em uma string longa e densa sem espaços. Ele se parece com isso:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
Ele não é criptografado, então qualquer pessoa pode recuperar as informações do seu conteúdo.
Mas ele é assinado. Assim, quando você recebe um token que você emitiu, você pode verificar que foi realmente você quem o emitiu.
Dessa forma, você pode criar um token com um prazo de expiração, digamos, de 1 semana. E então, quando o usuário voltar no dia seguinte com o token, você sabe que ele ainda está logado no seu sistema.
Depois de uma semana, o token expirará e o usuário não estará autorizado, precisando fazer login novamente para obter um novo token. E se o usuário (ou uma terceira parte) tentar modificar o token para alterar a expiração, você seria capaz de descobrir isso, pois as assinaturas não iriam corresponder.
Se você quiser brincar com tokens JWT e ver como eles funcionam, visite <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
## Instalar `PyJWT`
Nós precisamos instalar o `PyJWT` para criar e verificar os tokens JWT em Python.
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o `pyjwt`:
<div class="termy">
```console
$ pip install pyjwt
---> 100%
```
</div>
/// info | Informação
Se você pretente utilizar algoritmos de assinatura digital como o RSA ou o ECDSA, você deve instalar a dependência da biblioteca de criptografia `pyjwt[crypto]`.
Você pode ler mais sobre isso na <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">documentação de instalação do PyJWT</a>.
///
## Hashing de senhas
"Hashing" significa converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece um monte de caracteres sem sentido.
Sempre que você passar exatamente o mesmo conteúdo (exatamente a mesma senha), você obterá exatamente o mesmo resultado.
Mas não é possível converter os caracteres sem sentido de volta para a senha original.
### Por que usar hashing de senhas
Se o seu banco de dados for roubado, o invasor não terá as senhas em texto puro dos seus usuários, apenas os hashes.
Então, o invasor não poderá tentar usar essas senhas em outro sistema (como muitos usuários utilizam a mesma senha em vários lugares, isso seria perigoso).
## Instalar o `passlib`
O PassLib é uma excelente biblioteca Python para lidar com hashes de senhas.
Ele suporta muitos algoritmos de hashing seguros e utilitários para trabalhar com eles.
O algoritmo recomendado é o "Bcrypt".
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o PassLib com Bcrypt:
<div class="termy">
```console
$ pip install "passlib[bcrypt]"
---> 100%
```
</div>
/// tip | Dica
Com o `passlib`, você poderia até configurá-lo para ser capaz de ler senhas criadas pelo **Django**, um plug-in de segurança do **Flask** ou muitos outros.
Assim, você poderia, por exemplo, compartilhar os mesmos dados de um aplicativo Django em um banco de dados com um aplicativo FastAPI. Ou migrar gradualmente uma aplicação Django usando o mesmo banco de dados.
E seus usuários poderiam fazer login tanto pela sua aplicação Django quanto pela sua aplicação **FastAPI**, ao mesmo tempo.
///
## Criar o hash e verificar as senhas
Importe as ferramentas que nós precisamos de `passlib`.
Crie um "contexto" do PassLib. Este será usado para criar o hash e verificar as senhas.
/// tip | Dica
O contexto do PassLib também possui funcionalidades para usar diferentes algoritmos de hashing, incluindo algoritmos antigos que estão obsoletos, apenas para permitir verificá-los, etc.
Por exemplo, você poderia usá-lo para ler e verificar senhas geradas por outro sistema (como Django), mas criar o hash de novas senhas com um algoritmo diferente, como o Bcrypt.
E ser compatível com todos eles ao mesmo tempo.
///
Crie uma função utilitária para criar o hash de uma senha fornecida pelo usuário.
E outra função utilitária para verificar se uma senha recebida corresponde ao hash armazenado.
E outra para autenticar e retornar um usuário.
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
/// note | Nota
Se você verificar o novo banco de dados (falso) `fake_users_db`, você verá como o hash da senha se parece agora: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
///
## Manipular tokens JWT
Importe os módulos instalados.
Crie uma chave secreta aleatória que será usada para assinar os tokens JWT.
Para gerar uma chave secreta aleatória e segura, use o comando:
<div class="termy">
```console
$ openssl rand -hex 32
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
```
</div>
E copie a saída para a variável `SECRET_KEY` (não use a do exemplo).
Crie uma variável `ALGORITHM` com o algoritmo usado para assinar o token JWT e defina como `"HS256"`.
Crie uma variável para a expiração do token.
Defina um modelo Pydantic que será usado no endpoint de token para a resposta.
Crie uma função utilitária para gerar um novo token de acesso.
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
## Atualize as dependências
Atualize `get_current_user` para receber o mesmo token de antes, mas desta vez, usando tokens JWT.
Decodifique o token recebido, verifique-o e retorne o usuário atual.
Se o token for inválido, retorne um erro HTTP imediatamente.
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
## Atualize a *operação de rota* `/token`
Crie um `timedelta` com o tempo de expiração do token.
Crie um token de acesso JWT real e o retorne.
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
### Detalhes técnicos sobre o "sujeito" `sub` do JWT
A especificação JWT diz que existe uma chave `sub`, com o sujeito do token.
É opcional usá-la, mas é onde você colocaria a identificação do usuário, então nós estamos usando aqui.
O JWT pode ser usado para outras coisas além de identificar um usuário e permitir que ele execute operações diretamente na sua API.
Por exemplo, você poderia identificar um "carro" ou uma "postagem de blog".
Depois, você poderia adicionar permissões sobre essa entidade, como "dirigir" (para o carro) ou "editar" (para o blog).
E então, poderia dar esse token JWT para um usuário (ou bot), e ele poderia usá-lo para realizar essas ações (dirigir o carro ou editar o blog) sem sequer precisar ter uma conta, apenas com o token JWT que sua API gerou para isso.
Usando essas ideias, o JWT pode ser usado para cenários muito mais sofisticados.
Nesses casos, várias dessas entidades poderiam ter o mesmo ID, digamos `foo` (um usuário `foo`, um carro `foo` e uma postagem de blog `foo`).
Então, para evitar colisões de ID, ao criar o token JWT para o usuário, você poderia prefixar o valor da chave `sub`, por exemplo, com `username:`. Assim, neste exemplo, o valor de `sub` poderia ser: `username:johndoe`.
O importante a se lembrar é que a chave `sub` deve ter um identificador único em toda a aplicação e deve ser uma string.
## Testando
Execute o servidor e vá para a documentação: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Você verá a interface de usuário assim:
<img src="/img/tutorial/security/image07.png">
Autorize a aplicação da mesma maneira que antes.
Usando as credenciais:
Username: `johndoe`
Password: `secret`
/// check | Verifique
Observe que em nenhuma parte do código está a senha em texto puro "`secret`", nós temos apenas o hash.
///
<img src="/img/tutorial/security/image08.png">
Chame o endpoint `/users/me/`, você receberá o retorno como:
```JSON
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false
}
```
<img src="/img/tutorial/security/image09.png">
Se você abrir as ferramentas de desenvolvedor, poderá ver que os dados enviados incluem apenas o token. A senha é enviada apenas na primeira requisição para autenticar o usuário e obter o token de acesso, mas não é enviada nas próximas requisições:
<img src="/img/tutorial/security/image10.png">
/// note | Nota
Perceba que o cabeçalho `Authorization`, com o valor que começa com `Bearer `.
///
## Uso avançado com `scopes`
O OAuth2 tem a noção de "scopes" (escopos).
Você pode usá-los para adicionar um conjunto específico de permissões a um token JWT.
Então, você pode dar este token diretamente a um usuário ou a uma terceira parte para interagir com sua API com um conjunto de restrições.
Você pode aprender como usá-los e como eles são integrados ao **FastAPI** mais adiante no **Guia Avançado do Usuário**.
## Recapitulação
Com o que você viu até agora, você pode configurar uma aplicação **FastAPI** segura usando padrões como OAuth2 e JWT.
Em quase qualquer framework, lidar com a segurança se torna rapidamente um assunto bastante complexo.
Muitos pacotes que simplificam bastante isso precisam fazer muitas concessões com o modelo de dados, o banco de dados e os recursos disponíveis. E alguns desses pacotes que simplificam demais na verdade têm falhas de segurança subjacentes.
---
O **FastAPI** não faz nenhuma concessão com nenhum banco de dados, modelo de dados ou ferramenta.
Ele oferece toda a flexibilidade para você escolher as opções que melhor se ajustam ao seu projeto.
E você pode usar diretamente muitos pacotes bem mantidos e amplamente utilizados, como `passlib` e `PyJWT`, porque o **FastAPI** não exige mecanismos complexos para integrar pacotes externos.
Mas ele fornece as ferramentas para simplificar o processo o máximo possível, sem comprometer a flexibilidade, robustez ou segurança.
E você pode usar e implementar protocolos padrão seguros, como o OAuth2, de uma maneira relativamente simples.
Você pode aprender mais no **Guia Avançado do Usuário** sobre como usar os "scopes" do OAuth2 para um sistema de permissões mais refinado, seguindo esses mesmos padrões. O OAuth2 com scopes é o mecanismo usado por muitos provedores grandes de autenticação, como o Facebook, Google, GitHub, Microsoft, Twitter, etc. para autorizar aplicativos de terceiros a interagir com suas APIs em nome de seus usuários.

99
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` основан на <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, и, к счастью, мы можем использовать его (`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 *}
## Запуск тестов
Вы можете запустить свои тесты как обычно:
<div class="termy">
```console
$ pytest
---> 100%
```
</div>
## Подробнее
Маркер `@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` из <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.
///
## Вызов других асинхронных функций
Теперь тестовая функция стала асинхронной, поэтому внутри нее вы можете вызывать также и другие `async` функции, не связанные с отправлением запросов в ваше FastAPI приложение. Как если бы вы вызывали их в любом другом месте вашего кода.
/// tip | Подсказка
Если вы столкнулись с `RuntimeError: Task attached to a different loop` при вызове асинхронных функций в ваших тестах (например, при использовании <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB's MotorClient</a>), то не забывайте инициализировать объекты, которым нужен цикл событий (event loop), только внутри асинхронных функций, например, в `'@app.on_event("startup")` callback.
///

556
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`.
<img src="/img/tutorial/bigger-applications/package.svg">
Та же самая файловая структура приложения, но с комментариями:
```
.
├── 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`.
Вспомните, как выглядит файловая структура нашего приложения:
<img src="/img/tutorial/bigger-applications/package.svg">
---
Две точки `..`, как в данном примере:
```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 вы можете из <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">официальной документации Python о модулях</a>
///
### Избегайте конфликтов имен
Вместо того чтобы импортировать только переменную `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
Теперь запустите приложение:
<div class="termy">
```console
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Откройте документацию по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите автоматическую API документацию. Она включает в себя маршруты из суб-модулей, используя верные маршруты, префиксы и теги:
<img src="/img/tutorial/bigger-applications/image01.png">
## Подключение существующего маршрута через новый префикс (`prefix`)
Вы можете использовать `.include_router()` несколько раз с одним и тем же маршрутом, применив различные префиксы.
Это может быть полезным, если нужно предоставить доступ к одному и тому же API через различные префиксы, например, `/api/v1` и `/api/latest`.
Это продвинутый способ, который вам может и не пригодится. Мы приводим его на случай, если вдруг вам это понадобится.
## Включение одного маршрутизатора (`APIRouter`) в другой
Точно так же, как вы включаете `APIRouter` в приложение `FastAPI`, вы можете включить `APIRouter` в другой `APIRouter`:
```Python
router.include_router(other_router)
```
Удостоверьтесь, что вы сделали это до того, как подключить маршрутизатор (`router`) к вашему `FastAPI` приложению, и *эндпоинты* маршрутизатора `other_router` были также подключены.

4
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`.

2
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.7"
from starlette import status as status

11
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",
@ -41,7 +42,7 @@ classifiers = [
"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 +61,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 +80,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

2
requirements-tests.txt

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

65
.github/actions/notify-translations/app/main.py → 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()

401
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()

221
scripts/sponsors.py

@ -0,0 +1,221 @@
import logging
import secrets
import subprocess
from collections import defaultdict
from pathlib import Path
from typing import Any
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"
sponsors_query = """
query Q($after: String) {
user(login: "tiangolo") {
sponsorshipsAsMaintainer(first: 100, after: $after) {
edges {
cursor
node {
sponsorEntity {
... on Organization {
login
avatarUrl
url
}
... on User {
login
avatarUrl
url
}
}
tier {
name
monthlyPriceInDollars
}
}
}
}
}
}
"""
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):
sponsors_token: SecretStr
pr_token: SecretStr
github_repository: str
httpx_timeout: int = 30
def get_graphql_response(
*,
settings: Settings,
query: str,
after: str | None = None,
) -> dict[str, Any]:
headers = {"Authorization": f"token {settings.sponsors_token.get_secret_value()}"}
variables = {"after": after}
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}")
logging.error(response.text)
raise RuntimeError(response.text)
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
def get_graphql_sponsor_edges(
*, settings: Settings, after: str | None = None
) -> list[SponsorshipAsMaintainerEdge]:
data = get_graphql_response(settings=settings, query=sponsors_query, after=after)
graphql_response = SponsorsResponse.model_validate(data)
return graphql_response.data.user.sponsorshipsAsMaintainer.edges
def get_individual_sponsors(
settings: Settings,
) -> defaultdict[float, dict[str, SponsorEntity]]:
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 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.pr_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
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)
github_sponsors = {
"sponsors": sponsors,
}
# For local development
# github_sponsors_path = Path("../docs/en/data/github_sponsors.yml")
github_sponsors_path = Path("./docs/en/data/github_sponsors.yml")
updated = update_content(
content_path=github_sponsors_path, new_content=github_sponsors
)
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-sponsors-{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(github_sponsors_path),
],
check=True,
)
logging.info("Committing updated file")
message = "👥 Update FastAPI People - Sponsors"
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()

21
tests/test_tutorial/test_body_multiple_params/test_tutorial001.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.tutorial001 import app
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py39", marks=needs_py39),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client

206
tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py

@ -1,206 +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.tutorial001_an import app
client = TestClient(app)
return client
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
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": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"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"}
),
},
},
"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"},
}
},
},
}
},
}

213
tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py

@ -1,213 +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.tutorial001_an_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
@needs_py310
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
@needs_py310
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
@needs_py310
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
@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": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"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"}
),
},
},
"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"},
}
},
},
}
},
}

213
tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py

@ -1,213 +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.tutorial001_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
@needs_py39
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
@needs_py39
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
@needs_py39
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
@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": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"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"}
),
},
},
"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"},
}
},
},
}
},
}

213
tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py

@ -1,213 +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.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
@needs_py310
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
@needs_py310
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
@needs_py310
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
@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": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"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"}
),
},
},
"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"},
}
},
},
}
},
}

18
tests/test_tutorial/test_body_nested_models/test_tutorial009.py

@ -1,13 +1,23 @@
import importlib
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_nested_models.tutorial009 import app
@pytest.fixture(
name="client",
params=[
"tutorial009",
pytest.param("tutorial009_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client

128
tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py

@ -1,128 +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_nested_models.tutorial009_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_post_body(client: TestClient):
data = {"2": 2.2, "3": 3.3}
response = client.post("/index-weights/", json=data)
assert response.status_code == 200, response.text
assert response.json() == data
@needs_py39
def test_post_invalid_body(client: TestClient):
data = {"foo": 2.2, "3": 3.3}
response = client.post("/index-weights/", json=data)
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["body", "foo", "[key]"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "__key__"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
@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": {
"/index-weights/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Index Weights",
"operationId": "create_index_weights_index_weights__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"title": "Weights",
"type": "object",
"additionalProperties": {"type": "number"},
}
}
},
"required": True,
},
}
}
},
"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"},
}
},
},
}
},
}

19
tests/test_tutorial/test_body_updates/test_tutorial001.py

@ -1,14 +1,23 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_pydanticv1, needs_pydanticv2
from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
def get_client():
from docs_src.body_updates.tutorial001 import app
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
pytest.param("tutorial001_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_updates.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client

317
tests/test_tutorial/test_body_updates/test_tutorial001_py310.py

@ -1,317 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
def get_client():
from docs_src.body_updates.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_get(client: TestClient):
response = client.get("/items/baz")
assert response.status_code == 200, response.text
assert response.json() == {
"name": "Baz",
"description": None,
"price": 50.2,
"tax": 10.5,
"tags": [],
}
@needs_py310
def test_put(client: TestClient):
response = client.put(
"/items/bar", json={"name": "Barz", "price": 3, "description": None}
)
assert response.json() == {
"name": "Barz",
"description": None,
"price": 3,
"tax": 10.5,
"tags": [],
}
@needs_py310
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
},
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"Item": {
"type": "object",
"title": "Item",
"properties": {
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {
"anyOf": [{"type": "number"}, {"type": "null"}],
"title": "Price",
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_py310
@needs_pydanticv1
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
},
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

317
tests/test_tutorial/test_body_updates/test_tutorial001_py39.py

@ -1,317 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
@pytest.fixture(name="client")
def get_client():
from docs_src.body_updates.tutorial001_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get(client: TestClient):
response = client.get("/items/baz")
assert response.status_code == 200, response.text
assert response.json() == {
"name": "Baz",
"description": None,
"price": 50.2,
"tax": 10.5,
"tags": [],
}
@needs_py39
def test_put(client: TestClient):
response = client.put(
"/items/bar", json={"name": "Barz", "price": 3, "description": None}
)
assert response.json() == {
"name": "Barz",
"description": None,
"price": 3,
"tax": 10.5,
"tags": [],
}
@needs_py39
@needs_pydanticv2
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
},
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"Item": {
"type": "object",
"title": "Item",
"properties": {
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Description",
},
"price": {
"anyOf": [{"type": "number"}, {"type": "null"}],
"title": "Price",
},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
# TODO: remove when deprecating Pydantic v1
@needs_py39
@needs_pydanticv1
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
},
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
"tax": {"title": "Tax", "type": "number", "default": 10.5},
"tags": {
"title": "Tags",
"type": "array",
"items": {"type": "string"},
"default": [],
},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

29
tests/test_tutorial/test_cookie_params/test_tutorial001.py

@ -1,8 +1,27 @@
import importlib
from types import ModuleType
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.cookie_params.tutorial001 import app
from ...utils import needs_py39, needs_py310
@pytest.fixture(
name="mod",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py39", marks=needs_py39),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_mod(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.cookie_params.{request.param}")
return mod
@pytest.mark.parametrize(
@ -19,15 +38,15 @@ from docs_src.cookie_params.tutorial001 import app
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
],
)
def test(path, cookies, expected_status, expected_response):
client = TestClient(app, cookies=cookies)
def test(path, cookies, expected_status, expected_response, mod: ModuleType):
client = TestClient(mod.app, cookies=cookies)
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
client = TestClient(app)
def test_openapi_schema(mod: ModuleType):
client = TestClient(mod.app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {

108
tests/test_tutorial/test_cookie_params/test_tutorial001_an.py

@ -1,108 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.cookie_params.tutorial001_an import app
@pytest.mark.parametrize(
"path,cookies,expected_status,expected_response",
[
("/items", None, 200, {"ads_id": None}),
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
(
"/items",
{"ads_id": "ads_track", "session": "cookiesession"},
200,
{"ads_id": "ads_track"},
),
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
],
)
def test(path, cookies, expected_status, expected_response):
client = TestClient(app, cookies=cookies)
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Ads Id",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Ads Id", "type": "string"}
),
"name": "ads_id",
"in": "cookie",
}
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

114
tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py

@ -1,114 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@needs_py310
@pytest.mark.parametrize(
"path,cookies,expected_status,expected_response",
[
("/items", None, 200, {"ads_id": None}),
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
(
"/items",
{"ads_id": "ads_track", "session": "cookiesession"},
200,
{"ads_id": "ads_track"},
),
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
],
)
def test(path, cookies, expected_status, expected_response):
from docs_src.cookie_params.tutorial001_an_py310 import app
client = TestClient(app, cookies=cookies)
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema():
from docs_src.cookie_params.tutorial001_an_py310 import app
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Ads Id",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Ads Id", "type": "string"}
),
"name": "ads_id",
"in": "cookie",
}
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

114
tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py

@ -1,114 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39
@needs_py39
@pytest.mark.parametrize(
"path,cookies,expected_status,expected_response",
[
("/items", None, 200, {"ads_id": None}),
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
(
"/items",
{"ads_id": "ads_track", "session": "cookiesession"},
200,
{"ads_id": "ads_track"},
),
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
],
)
def test(path, cookies, expected_status, expected_response):
from docs_src.cookie_params.tutorial001_an_py39 import app
client = TestClient(app, cookies=cookies)
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py39
def test_openapi_schema():
from docs_src.cookie_params.tutorial001_an_py39 import app
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Ads Id",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Ads Id", "type": "string"}
),
"name": "ads_id",
"in": "cookie",
}
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

114
tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py

@ -1,114 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@needs_py310
@pytest.mark.parametrize(
"path,cookies,expected_status,expected_response",
[
("/items", None, 200, {"ads_id": None}),
("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
(
"/items",
{"ads_id": "ads_track", "session": "cookiesession"},
200,
{"ads_id": "ads_track"},
),
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
],
)
def test(path, cookies, expected_status, expected_response):
from docs_src.cookie_params.tutorial001_py310 import app
client = TestClient(app, cookies=cookies)
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema():
from docs_src.cookie_params.tutorial001_py310 import app
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Ads Id",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Ads Id", "type": "string"}
),
"name": "ads_id",
"in": "cookie",
}
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

25
tests/test_tutorial/test_dependencies/test_tutorial001.py

@ -1,10 +1,27 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial001 import app
from ...utils import needs_py39, needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py39", marks=needs_py39),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
@pytest.mark.parametrize(
@ -17,13 +34,13 @@ client = TestClient(app)
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
],
)
def test_get(path, expected_status, expected_response):
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {

183
tests/test_tutorial/test_dependencies/test_tutorial001_an.py

@ -1,183 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial001_an import app
client = TestClient(app)
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
],
)
def test_get(path, expected_status, expected_response):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
"/users/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

191
tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py

@ -1,191 +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.dependencies.tutorial001_an_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
],
)
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@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": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
"/users/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

191
tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py

@ -1,191 +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.dependencies.tutorial001_an_py39 import app
client = TestClient(app)
return client
@needs_py39
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
],
)
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@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": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
"/users/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

191
tests/test_tutorial/test_dependencies/test_tutorial001_py310.py

@ -1,191 +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.dependencies.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
("/items", 200, {"q": None, "skip": 0, "limit": 100}),
("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
("/users", 200, {"q": None, "skip": 0, "limit": 100}),
],
)
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@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": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
"/users/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
},
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

25
tests/test_tutorial/test_dependencies/test_tutorial004.py

@ -1,10 +1,27 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial004 import app
from ...utils import needs_py39, needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial004",
pytest.param("tutorial004_py310", marks=needs_py310),
"tutorial004_an",
pytest.param("tutorial004_an_py39", marks=needs_py39),
pytest.param("tutorial004_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
@pytest.mark.parametrize(
@ -55,13 +72,13 @@ client = TestClient(app)
),
],
)
def test_get(path, expected_status, expected_response):
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {

162
tests/test_tutorial/test_dependencies/test_tutorial004_an.py

@ -1,162 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial004_an import app
client = TestClient(app)
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
(
"/items",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
]
},
),
(
"/items?q=foo",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
],
"q": "foo",
},
),
(
"/items?q=foo&skip=1",
200,
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
),
(
"/items?q=bar&limit=2",
200,
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
),
(
"/items?q=bar&skip=1&limit=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
(
"/items?limit=1&q=bar&skip=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
],
)
def test_get(path, expected_status, expected_response):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

170
tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py

@ -1,170 +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.dependencies.tutorial004_an_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
(
"/items",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
]
},
),
(
"/items?q=foo",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
],
"q": "foo",
},
),
(
"/items?q=foo&skip=1",
200,
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
),
(
"/items?q=bar&limit=2",
200,
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
),
(
"/items?q=bar&skip=1&limit=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
(
"/items?limit=1&q=bar&skip=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
],
)
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@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": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

170
tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py

@ -1,170 +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.dependencies.tutorial004_an_py39 import app
client = TestClient(app)
return client
@needs_py39
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
(
"/items",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
]
},
),
(
"/items?q=foo",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
],
"q": "foo",
},
),
(
"/items?q=foo&skip=1",
200,
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
),
(
"/items?q=bar&limit=2",
200,
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
),
(
"/items?q=bar&skip=1&limit=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
(
"/items?limit=1&q=bar&skip=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
],
)
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@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": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

170
tests/test_tutorial/test_dependencies/test_tutorial004_py310.py

@ -1,170 +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.dependencies.tutorial004_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,expected_status,expected_response",
[
(
"/items",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
]
},
),
(
"/items?q=foo",
200,
{
"items": [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
],
"q": "foo",
},
),
(
"/items?q=foo&skip=1",
200,
{"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
),
(
"/items?q=bar&limit=2",
200,
{"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
),
(
"/items?q=bar&skip=1&limit=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
(
"/items?limit=1&q=bar&skip=1",
200,
{"items": [{"item_name": "Bar"}], "q": "bar"},
),
],
)
def test_get(path, expected_status, expected_response, client: TestClient):
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@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": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Limit",
"type": "integer",
"default": 100,
},
"name": "limit",
"in": "query",
},
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

30
tests/test_tutorial/test_dependencies/test_tutorial006.py

@ -1,12 +1,28 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial006 import app
from ...utils import needs_py39
@pytest.fixture(
name="client",
params=[
"tutorial006",
"tutorial006_an",
pytest.param("tutorial006_an_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_get_no_headers():
def test_get_no_headers(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
@ -45,13 +61,13 @@ def test_get_no_headers():
)
def test_get_invalid_one_header():
def test_get_invalid_one_header(client: TestClient):
response = client.get("/items/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_get_invalid_second_header():
def test_get_invalid_second_header(client: TestClient):
response = client.get(
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
@ -59,7 +75,7 @@ def test_get_invalid_second_header():
assert response.json() == {"detail": "X-Key header invalid"}
def test_get_valid_headers():
def test_get_valid_headers(client: TestClient):
response = client.get(
"/items/",
headers={
@ -71,7 +87,7 @@ def test_get_valid_headers():
assert response.json() == [{"item": "Foo"}, {"item": "Bar"}]
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() == {

149
tests/test_tutorial/test_dependencies/test_tutorial006_an.py

@ -1,149 +0,0 @@
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial006_an import app
client = TestClient(app)
def test_get_no_headers():
response = client.get("/items/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-key"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-key"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_get_invalid_one_header():
response = client.get("/items/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_get_invalid_second_header():
response = client.get(
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Key header invalid"}
def test_get_valid_headers():
response = client.get(
"/items/",
headers={
"X-Token": "fake-super-secret-token",
"X-Key": "fake-super-secret-key",
},
)
assert response.status_code == 200, response.text
assert response.json() == [{"item": "Foo"}, {"item": "Bar"}]
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": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
{
"required": True,
"schema": {"title": "X-Key", "type": "string"},
"name": "x-key",
"in": "header",
},
],
}
}
},
"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"},
}
},
},
}
},
}

161
tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py

@ -1,161 +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.dependencies.tutorial006_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get_no_headers(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-key"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-key"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_get_invalid_one_header(client: TestClient):
response = client.get("/items/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
@needs_py39
def test_get_invalid_second_header(client: TestClient):
response = client.get(
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Key header invalid"}
@needs_py39
def test_get_valid_headers(client: TestClient):
response = client.get(
"/items/",
headers={
"X-Token": "fake-super-secret-token",
"X-Key": "fake-super-secret-key",
},
)
assert response.status_code == 200, response.text
assert response.json() == [{"item": "Foo"}, {"item": "Bar"}]
@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": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
{
"required": True,
"schema": {"title": "X-Key", "type": "string"},
"name": "x-key",
"in": "header",
},
],
}
}
},
"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"},
}
},
},
}
},
}

26
tests/test_tutorial/test_dependencies/test_tutorial008b.py

@ -1,23 +1,39 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial008b import app
from ...utils import needs_py39
@pytest.fixture(
name="client",
params=[
"tutorial008b",
"tutorial008b_an",
pytest.param("tutorial008b_an_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_get_no_item():
def test_get_no_item(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found"}
def test_owner_error():
def test_owner_error(client: TestClient):
response = client.get("/items/plumbus")
assert response.status_code == 400, response.text
assert response.json() == {"detail": "Owner error: Rick"}
def test_get_item():
def test_get_item(client: TestClient):
response = client.get("/items/portal-gun")
assert response.status_code == 200, response.text
assert response.json() == {"description": "Gun to create portals", "owner": "Rick"}

23
tests/test_tutorial/test_dependencies/test_tutorial008b_an.py

@ -1,23 +0,0 @@
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial008b_an import app
client = TestClient(app)
def test_get_no_item():
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found"}
def test_owner_error():
response = client.get("/items/plumbus")
assert response.status_code == 400, response.text
assert response.json() == {"detail": "Owner error: Rick"}
def test_get_item():
response = client.get("/items/portal-gun")
assert response.status_code == 200, response.text
assert response.json() == {"description": "Gun to create portals", "owner": "Rick"}

33
tests/test_tutorial/test_dependencies/test_tutorial008b_an_py39.py

@ -1,33 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.dependencies.tutorial008b_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get_no_item(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found"}
@needs_py39
def test_owner_error(client: TestClient):
response = client.get("/items/plumbus")
assert response.status_code == 400, response.text
assert response.json() == {"detail": "Owner error: Rick"}
@needs_py39
def test_get_item(client: TestClient):
response = client.get("/items/portal-gun")
assert response.status_code == 200, response.text
assert response.json() == {"description": "Gun to create portals", "owner": "Rick"}

36
tests/test_tutorial/test_dependencies/test_tutorial008c.py

@ -1,38 +1,50 @@
import importlib
from types import ModuleType
import pytest
from fastapi.exceptions import FastAPIError
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.dependencies.tutorial008c import app
@pytest.fixture(
name="mod",
params=[
"tutorial008c",
"tutorial008c_an",
pytest.param("tutorial008c_an_py39", marks=needs_py39),
],
)
def get_mod(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
client = TestClient(app)
return client
return mod
def test_get_no_item(client: TestClient):
def test_get_no_item(mod: ModuleType):
client = TestClient(mod.app)
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
def test_get(client: TestClient):
def test_get(mod: ModuleType):
client = TestClient(mod.app)
response = client.get("/items/plumbus")
assert response.status_code == 200, response.text
assert response.json() == "plumbus"
def test_fastapi_error(client: TestClient):
def test_fastapi_error(mod: ModuleType):
client = TestClient(mod.app)
with pytest.raises(FastAPIError) as exc_info:
client.get("/items/portal-gun")
assert "No response object was returned" in exc_info.value.args[0]
def test_internal_server_error():
from docs_src.dependencies.tutorial008c import app
client = TestClient(app, raise_server_exceptions=False)
def test_internal_server_error(mod: ModuleType):
client = TestClient(mod.app, raise_server_exceptions=False)
response = client.get("/items/portal-gun")
assert response.status_code == 500, response.text
assert response.text == "Internal Server Error"

38
tests/test_tutorial/test_dependencies/test_tutorial008c_an.py

@ -1,38 +0,0 @@
import pytest
from fastapi.exceptions import FastAPIError
from fastapi.testclient import TestClient
@pytest.fixture(name="client")
def get_client():
from docs_src.dependencies.tutorial008c_an import app
client = TestClient(app)
return client
def test_get_no_item(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
def test_get(client: TestClient):
response = client.get("/items/plumbus")
assert response.status_code == 200, response.text
assert response.json() == "plumbus"
def test_fastapi_error(client: TestClient):
with pytest.raises(FastAPIError) as exc_info:
client.get("/items/portal-gun")
assert "No response object was returned" in exc_info.value.args[0]
def test_internal_server_error():
from docs_src.dependencies.tutorial008c_an import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/items/portal-gun")
assert response.status_code == 500, response.text
assert response.text == "Internal Server Error"

44
tests/test_tutorial/test_dependencies/test_tutorial008c_an_py39.py

@ -1,44 +0,0 @@
import pytest
from fastapi.exceptions import FastAPIError
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.dependencies.tutorial008c_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get_no_item(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
@needs_py39
def test_get(client: TestClient):
response = client.get("/items/plumbus")
assert response.status_code == 200, response.text
assert response.json() == "plumbus"
@needs_py39
def test_fastapi_error(client: TestClient):
with pytest.raises(FastAPIError) as exc_info:
client.get("/items/portal-gun")
assert "No response object was returned" in exc_info.value.args[0]
@needs_py39
def test_internal_server_error():
from docs_src.dependencies.tutorial008c_an_py39 import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/items/portal-gun")
assert response.status_code == 500, response.text
assert response.text == "Internal Server Error"

40
tests/test_tutorial/test_dependencies/test_tutorial008d.py

@ -1,41 +1,51 @@
import importlib
from types import ModuleType
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.dependencies.tutorial008d import app
@pytest.fixture(
name="mod",
params=[
"tutorial008d",
"tutorial008d_an",
pytest.param("tutorial008d_an_py39", marks=needs_py39),
],
)
def get_mod(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
client = TestClient(app)
return client
return mod
def test_get_no_item(client: TestClient):
def test_get_no_item(mod: ModuleType):
client = TestClient(mod.app)
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
def test_get(client: TestClient):
def test_get(mod: ModuleType):
client = TestClient(mod.app)
response = client.get("/items/plumbus")
assert response.status_code == 200, response.text
assert response.json() == "plumbus"
def test_internal_error(client: TestClient):
from docs_src.dependencies.tutorial008d import InternalError
with pytest.raises(InternalError) as exc_info:
def test_internal_error(mod: ModuleType):
client = TestClient(mod.app)
with pytest.raises(mod.InternalError) as exc_info:
client.get("/items/portal-gun")
assert (
exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick"
)
def test_internal_server_error():
from docs_src.dependencies.tutorial008d import app
client = TestClient(app, raise_server_exceptions=False)
def test_internal_server_error(mod: ModuleType):
client = TestClient(mod.app, raise_server_exceptions=False)
response = client.get("/items/portal-gun")
assert response.status_code == 500, response.text
assert response.text == "Internal Server Error"

41
tests/test_tutorial/test_dependencies/test_tutorial008d_an.py

@ -1,41 +0,0 @@
import pytest
from fastapi.testclient import TestClient
@pytest.fixture(name="client")
def get_client():
from docs_src.dependencies.tutorial008d_an import app
client = TestClient(app)
return client
def test_get_no_item(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
def test_get(client: TestClient):
response = client.get("/items/plumbus")
assert response.status_code == 200, response.text
assert response.json() == "plumbus"
def test_internal_error(client: TestClient):
from docs_src.dependencies.tutorial008d_an import InternalError
with pytest.raises(InternalError) as exc_info:
client.get("/items/portal-gun")
assert (
exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick"
)
def test_internal_server_error():
from docs_src.dependencies.tutorial008d_an import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/items/portal-gun")
assert response.status_code == 500, response.text
assert response.text == "Internal Server Error"

47
tests/test_tutorial/test_dependencies/test_tutorial008d_an_py39.py

@ -1,47 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.dependencies.tutorial008d_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get_no_item(client: TestClient):
response = client.get("/items/foo")
assert response.status_code == 404, response.text
assert response.json() == {"detail": "Item not found, there's only a plumbus here"}
@needs_py39
def test_get(client: TestClient):
response = client.get("/items/plumbus")
assert response.status_code == 200, response.text
assert response.json() == "plumbus"
@needs_py39
def test_internal_error(client: TestClient):
from docs_src.dependencies.tutorial008d_an_py39 import InternalError
with pytest.raises(InternalError) as exc_info:
client.get("/items/portal-gun")
assert (
exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick"
)
@needs_py39
def test_internal_server_error():
from docs_src.dependencies.tutorial008d_an_py39 import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/items/portal-gun")
assert response.status_code == 500, response.text
assert response.text == "Internal Server Error"

38
tests/test_tutorial/test_dependencies/test_tutorial012.py

@ -1,12 +1,28 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial012 import app
from ...utils import needs_py39
@pytest.fixture(
name="client",
params=[
"tutorial012",
"tutorial012_an",
pytest.param("tutorial012_an_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.dependencies.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_get_no_headers_items():
def test_get_no_headers_items(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
@ -45,7 +61,7 @@ def test_get_no_headers_items():
)
def test_get_no_headers_users():
def test_get_no_headers_users(client: TestClient):
response = client.get("/users/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
@ -84,19 +100,19 @@ def test_get_no_headers_users():
)
def test_get_invalid_one_header_items():
def test_get_invalid_one_header_items(client: TestClient):
response = client.get("/items/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_get_invalid_one_users():
def test_get_invalid_one_users(client: TestClient):
response = client.get("/users/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_get_invalid_second_header_items():
def test_get_invalid_second_header_items(client: TestClient):
response = client.get(
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
@ -104,7 +120,7 @@ def test_get_invalid_second_header_items():
assert response.json() == {"detail": "X-Key header invalid"}
def test_get_invalid_second_header_users():
def test_get_invalid_second_header_users(client: TestClient):
response = client.get(
"/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
@ -112,7 +128,7 @@ def test_get_invalid_second_header_users():
assert response.json() == {"detail": "X-Key header invalid"}
def test_get_valid_headers_items():
def test_get_valid_headers_items(client: TestClient):
response = client.get(
"/items/",
headers={
@ -124,7 +140,7 @@ def test_get_valid_headers_items():
assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}]
def test_get_valid_headers_users():
def test_get_valid_headers_users(client: TestClient):
response = client.get(
"/users/",
headers={
@ -136,7 +152,7 @@ def test_get_valid_headers_users():
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
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() == {

250
tests/test_tutorial/test_dependencies/test_tutorial012_an.py

@ -1,250 +0,0 @@
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial012_an import app
client = TestClient(app)
def test_get_no_headers_items():
response = client.get("/items/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-key"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-key"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_get_no_headers_users():
response = client.get("/users/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-key"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-key"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_get_invalid_one_header_items():
response = client.get("/items/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_get_invalid_one_users():
response = client.get("/users/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_get_invalid_second_header_items():
response = client.get(
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Key header invalid"}
def test_get_invalid_second_header_users():
response = client.get(
"/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Key header invalid"}
def test_get_valid_headers_items():
response = client.get(
"/items/",
headers={
"X-Token": "fake-super-secret-token",
"X-Key": "fake-super-secret-key",
},
)
assert response.status_code == 200, response.text
assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}]
def test_get_valid_headers_users():
response = client.get(
"/users/",
headers={
"X-Token": "fake-super-secret-token",
"X-Key": "fake-super-secret-key",
},
)
assert response.status_code == 200, response.text
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
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",
"parameters": [
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
{
"required": True,
"schema": {"title": "X-Key", "type": "string"},
"name": "x-key",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/users/": {
"get": {
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
{
"required": True,
"schema": {"title": "X-Key", "type": "string"},
"name": "x-key",
"in": "header",
},
],
"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"},
},
},
}
},
}

266
tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py

@ -1,266 +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.dependencies.tutorial012_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get_no_headers_items(client: TestClient):
response = client.get("/items/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-key"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-key"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_get_no_headers_users(client: TestClient):
response = client.get("/users/")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-key"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-key"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_get_invalid_one_header_items(client: TestClient):
response = client.get("/items/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
@needs_py39
def test_get_invalid_one_users(client: TestClient):
response = client.get("/users/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
@needs_py39
def test_get_invalid_second_header_items(client: TestClient):
response = client.get(
"/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Key header invalid"}
@needs_py39
def test_get_invalid_second_header_users(client: TestClient):
response = client.get(
"/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"}
)
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Key header invalid"}
@needs_py39
def test_get_valid_headers_items(client: TestClient):
response = client.get(
"/items/",
headers={
"X-Token": "fake-super-secret-token",
"X-Key": "fake-super-secret-key",
},
)
assert response.status_code == 200, response.text
assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}]
@needs_py39
def test_get_valid_headers_users(client: TestClient):
response = client.get(
"/users/",
headers={
"X-Token": "fake-super-secret-token",
"X-Key": "fake-super-secret-key",
},
)
assert response.status_code == 200, response.text
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
@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": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
{
"required": True,
"schema": {"title": "X-Key", "type": "string"},
"name": "x-key",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/users/": {
"get": {
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
{
"required": True,
"schema": {"title": "X-Key", "type": "string"},
"name": "x-key",
"in": "header",
},
],
"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"},
},
},
}
},
}

26
tests/test_tutorial/test_extra_data_types/test_tutorial001.py

@ -1,12 +1,30 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.extra_data_types.tutorial001 import app
from ...utils import needs_py39, needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py39", marks=needs_py39),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.extra_data_types.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_extra_types():
def test_extra_types(client: TestClient):
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
data = {
"start_datetime": "2018-12-22T14:00:00+00:00",
@ -27,7 +45,7 @@ def test_extra_types():
assert response.json() == expected_response
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {

175
tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py

@ -1,175 +0,0 @@
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.extra_data_types.tutorial001_an import app
client = TestClient(app)
def test_extra_types():
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
data = {
"start_datetime": "2018-12-22T14:00:00+00:00",
"end_datetime": "2018-12-24T15:00:00+00:00",
"repeat_at": "15:30:00",
"process_after": 300,
}
expected_response = data.copy()
expected_response.update(
{
"start_process": "2018-12-22T14:05:00+00:00",
"duration": 176_100,
"item_id": item_id,
}
)
response = client.put(f"/items/{item_id}", json=data)
assert response.status_code == 200, response.text
assert response.json() == expected_response
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"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__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "Item Id",
"type": "string",
"format": "uuid",
},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": IsDict(
{
"allOf": [
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
],
"title": "Body",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
)
}
},
},
}
}
},
"components": {
"schemas": {
"Body_read_items_items__item_id__put": {
"title": "Body_read_items_items__item_id__put",
"type": "object",
"properties": {
"start_datetime": {
"title": "Start Datetime",
"type": "string",
"format": "date-time",
},
"end_datetime": {
"title": "End Datetime",
"type": "string",
"format": "date-time",
},
"repeat_at": IsDict(
{
"title": "Repeat At",
"anyOf": [
{"type": "string", "format": "time"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Repeat At",
"type": "string",
"format": "time",
}
),
"process_after": IsDict(
{
"title": "Process After",
"type": "string",
"format": "duration",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Process After",
"type": "number",
"format": "time-delta",
}
),
},
"required": ["start_datetime", "end_datetime", "process_after"],
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

184
tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py

@ -1,184 +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.extra_data_types.tutorial001_an_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_extra_types(client: TestClient):
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
data = {
"start_datetime": "2018-12-22T14:00:00+00:00",
"end_datetime": "2018-12-24T15:00:00+00:00",
"repeat_at": "15:30:00",
"process_after": 300,
}
expected_response = data.copy()
expected_response.update(
{
"start_process": "2018-12-22T14:05:00+00:00",
"duration": 176_100,
"item_id": item_id,
}
)
response = client.put(f"/items/{item_id}", json=data)
assert response.status_code == 200, response.text
assert response.json() == expected_response
@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": "Read Items",
"operationId": "read_items_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "Item Id",
"type": "string",
"format": "uuid",
},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": IsDict(
{
"allOf": [
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
],
"title": "Body",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
)
}
},
},
}
}
},
"components": {
"schemas": {
"Body_read_items_items__item_id__put": {
"title": "Body_read_items_items__item_id__put",
"type": "object",
"properties": {
"start_datetime": {
"title": "Start Datetime",
"type": "string",
"format": "date-time",
},
"end_datetime": {
"title": "End Datetime",
"type": "string",
"format": "date-time",
},
"repeat_at": IsDict(
{
"title": "Repeat At",
"anyOf": [
{"type": "string", "format": "time"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Repeat At",
"type": "string",
"format": "time",
}
),
"process_after": IsDict(
{
"title": "Process After",
"type": "string",
"format": "duration",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Process After",
"type": "number",
"format": "time-delta",
}
),
},
"required": ["start_datetime", "end_datetime", "process_after"],
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

184
tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py

@ -1,184 +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.extra_data_types.tutorial001_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_extra_types(client: TestClient):
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
data = {
"start_datetime": "2018-12-22T14:00:00+00:00",
"end_datetime": "2018-12-24T15:00:00+00:00",
"repeat_at": "15:30:00",
"process_after": 300,
}
expected_response = data.copy()
expected_response.update(
{
"start_process": "2018-12-22T14:05:00+00:00",
"duration": 176_100,
"item_id": item_id,
}
)
response = client.put(f"/items/{item_id}", json=data)
assert response.status_code == 200, response.text
assert response.json() == expected_response
@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": "Read Items",
"operationId": "read_items_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "Item Id",
"type": "string",
"format": "uuid",
},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": IsDict(
{
"allOf": [
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
],
"title": "Body",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
)
}
},
},
}
}
},
"components": {
"schemas": {
"Body_read_items_items__item_id__put": {
"title": "Body_read_items_items__item_id__put",
"type": "object",
"properties": {
"start_datetime": {
"title": "Start Datetime",
"type": "string",
"format": "date-time",
},
"end_datetime": {
"title": "End Datetime",
"type": "string",
"format": "date-time",
},
"repeat_at": IsDict(
{
"title": "Repeat At",
"anyOf": [
{"type": "string", "format": "time"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Repeat At",
"type": "string",
"format": "time",
}
),
"process_after": IsDict(
{
"title": "Process After",
"type": "string",
"format": "duration",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Process After",
"type": "number",
"format": "time-delta",
}
),
},
"required": ["start_datetime", "end_datetime", "process_after"],
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

184
tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py

@ -1,184 +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.extra_data_types.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_extra_types(client: TestClient):
item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
data = {
"start_datetime": "2018-12-22T14:00:00+00:00",
"end_datetime": "2018-12-24T15:00:00+00:00",
"repeat_at": "15:30:00",
"process_after": 300,
}
expected_response = data.copy()
expected_response.update(
{
"start_process": "2018-12-22T14:05:00+00:00",
"duration": 176_100,
"item_id": item_id,
}
)
response = client.put(f"/items/{item_id}", json=data)
assert response.status_code == 200, response.text
assert response.json() == expected_response
@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": "Read Items",
"operationId": "read_items_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "Item Id",
"type": "string",
"format": "uuid",
},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": IsDict(
{
"allOf": [
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
],
"title": "Body",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
)
}
},
},
}
}
},
"components": {
"schemas": {
"Body_read_items_items__item_id__put": {
"title": "Body_read_items_items__item_id__put",
"type": "object",
"properties": {
"start_datetime": {
"title": "Start Datetime",
"type": "string",
"format": "date-time",
},
"end_datetime": {
"title": "End Datetime",
"type": "string",
"format": "date-time",
},
"repeat_at": IsDict(
{
"title": "Repeat At",
"anyOf": [
{"type": "string", "format": "time"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Repeat At",
"type": "string",
"format": "time",
}
),
"process_after": IsDict(
{
"title": "Process After",
"type": "string",
"format": "duration",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "Process After",
"type": "number",
"format": "time-delta",
}
),
},
"required": ["start_datetime", "end_datetime", "process_after"],
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

25
tests/test_tutorial/test_extra_models/test_tutorial003.py

@ -1,12 +1,27 @@
import importlib
import pytest
from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
from docs_src.extra_models.tutorial003 import app
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial003",
pytest.param("tutorial003_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.extra_models.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_get_car():
def test_get_car(client: TestClient):
response = client.get("/items/item1")
assert response.status_code == 200, response.text
assert response.json() == {
@ -15,7 +30,7 @@ def test_get_car():
}
def test_get_plane():
def test_get_plane(client: TestClient):
response = client.get("/items/item2")
assert response.status_code == 200, response.text
assert response.json() == {
@ -25,7 +40,7 @@ def test_get_plane():
}
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() == {

144
tests/test_tutorial/test_extra_models/test_tutorial003_py310.py

@ -1,144 +0,0 @@
import pytest
from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.extra_models.tutorial003_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_get_car(client: TestClient):
response = client.get("/items/item1")
assert response.status_code == 200, response.text
assert response.json() == {
"description": "All my friends drive a low rider",
"type": "car",
}
@needs_py310
def test_get_plane(client: TestClient):
response = client.get("/items/item2")
assert response.status_code == 200, response.text
assert response.json() == {
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane",
"size": 5,
}
@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}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response Read Item Items Item Id Get",
"anyOf": [
{"$ref": "#/components/schemas/PlaneItem"},
{"$ref": "#/components/schemas/CarItem"},
],
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
}
}
},
"components": {
"schemas": {
"PlaneItem": {
"title": "PlaneItem",
"required": IsOneOf(
["description", "type", "size"],
# TODO: remove when deprecating Pydantic v1
["description", "size"],
),
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},
"type": {"title": "Type", "type": "string", "default": "plane"},
"size": {"title": "Size", "type": "integer"},
},
},
"CarItem": {
"title": "CarItem",
"required": IsOneOf(
["description", "type"],
# TODO: remove when deprecating Pydantic v1
["description"],
),
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},
"type": {"title": "Type", "type": "string", "default": "car"},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"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"},
}
},
},
}
},
}

23
tests/test_tutorial/test_extra_models/test_tutorial004.py

@ -1,11 +1,26 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from docs_src.extra_models.tutorial004 import app
from ...utils import needs_py39
@pytest.fixture(
name="client",
params=[
"tutorial004",
pytest.param("tutorial004_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.extra_models.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_get_items():
def test_get_items(client: TestClient):
response = client.get("/items/")
assert response.status_code == 200, response.text
assert response.json() == [
@ -14,7 +29,7 @@ def test_get_items():
]
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() == {

67
tests/test_tutorial/test_extra_models/test_tutorial004_py39.py

@ -1,67 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.extra_models.tutorial004_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get_items(client: TestClient):
response = client.get("/items/")
assert response.status_code == 200, response.text
assert response.json() == [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@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": {
"title": "Response Read Items Items Get",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
}
}
},
}
},
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "description"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
},
}
}
},
}

23
tests/test_tutorial/test_extra_models/test_tutorial005.py

@ -1,17 +1,32 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from docs_src.extra_models.tutorial005 import app
from ...utils import needs_py39
@pytest.fixture(
name="client",
params=[
"tutorial005",
pytest.param("tutorial005_py39", marks=needs_py39),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.extra_models.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_get_items():
def test_get_items(client: TestClient):
response = client.get("/keyword-weights/")
assert response.status_code == 200, response.text
assert response.json() == {"foo": 2.3, "bar": 3.4}
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() == {

51
tests/test_tutorial/test_extra_models/test_tutorial005_py39.py

@ -1,51 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.extra_models.tutorial005_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_get_items(client: TestClient):
response = client.get("/keyword-weights/")
assert response.status_code == 200, response.text
assert response.json() == {"foo": 2.3, "bar": 3.4}
@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": {
"/keyword-weights/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response Read Keyword Weights Keyword Weights Get",
"type": "object",
"additionalProperties": {"type": "number"},
}
}
},
}
},
"summary": "Read Keyword Weights",
"operationId": "read_keyword_weights_keyword_weights__get",
}
}
},
}

24
tests/test_tutorial/test_header_params/test_tutorial001.py

@ -1,10 +1,26 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.header_params.tutorial001 import app
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.header_params.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
@pytest.mark.parametrize(
@ -15,13 +31,13 @@ client = TestClient(app)
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
],
)
def test(path, headers, expected_status, expected_response):
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {

102
tests/test_tutorial/test_header_params/test_tutorial001_an.py

@ -1,102 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.header_params.tutorial001_an import app
client = TestClient(app)
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"User-Agent": "testclient"}),
("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
],
)
def test(path, headers, expected_status, expected_response):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "User-Agent",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "User-Agent", "type": "string"}
),
"name": "user-agent",
"in": "header",
}
],
}
}
},
"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"},
}
},
},
}
},
}

110
tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py

@ -1,110 +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.header_params.tutorial001_an_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"User-Agent": "testclient"}),
("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "User-Agent",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "User-Agent", "type": "string"}
),
"name": "user-agent",
"in": "header",
}
],
}
}
},
"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"},
}
},
},
}
},
}

110
tests/test_tutorial/test_header_params/test_tutorial001_py310.py

@ -1,110 +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.header_params.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"User-Agent": "testclient"}),
("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "User-Agent",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "User-Agent", "type": "string"}
),
"name": "user-agent",
"in": "header",
}
],
}
}
},
"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"},
}
},
},
}
},
}

25
tests/test_tutorial/test_header_params/test_tutorial002.py

@ -1,10 +1,27 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.header_params.tutorial002 import app
from ...utils import needs_py39, needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial002",
pytest.param("tutorial002_py310", marks=needs_py310),
"tutorial002_an",
pytest.param("tutorial002_an_py39", marks=needs_py39),
pytest.param("tutorial002_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.header_params.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
@pytest.mark.parametrize(
@ -26,13 +43,13 @@ client = TestClient(app)
),
],
)
def test(path, headers, expected_status, expected_response):
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {

113
tests/test_tutorial/test_header_params/test_tutorial002_an.py

@ -1,113 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.header_params.tutorial002_an import app
client = TestClient(app)
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"strange_header": None}),
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
(
"/items",
{"strange_header": "FastAPI test"},
200,
{"strange_header": "FastAPI test"},
),
(
"/items",
{"strange-header": "Not really underscore"},
200,
{"strange_header": None},
),
],
)
def test(path, headers, expected_status, expected_response):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Strange Header",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Strange Header", "type": "string"}
),
"name": "strange_header",
"in": "header",
}
],
}
}
},
"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"},
}
},
},
}
},
}

121
tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py

@ -1,121 +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.header_params.tutorial002_an_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"strange_header": None}),
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
(
"/items",
{"strange_header": "FastAPI test"},
200,
{"strange_header": "FastAPI test"},
),
(
"/items",
{"strange-header": "Not really underscore"},
200,
{"strange_header": None},
),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Strange Header",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Strange Header", "type": "string"}
),
"name": "strange_header",
"in": "header",
}
],
}
}
},
"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"},
}
},
},
}
},
}

124
tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py

@ -1,124 +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.header_params.tutorial002_an_py39 import app
client = TestClient(app)
return client
@needs_py39
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"strange_header": None}),
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
(
"/items",
{"strange_header": "FastAPI test"},
200,
{"strange_header": "FastAPI test"},
),
(
"/items",
{"strange-header": "Not really underscore"},
200,
{"strange_header": None},
),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py39
def test_openapi_schema():
from docs_src.header_params.tutorial002_an_py39 import app
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Strange Header",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Strange Header", "type": "string"}
),
"name": "strange_header",
"in": "header",
}
],
}
}
},
"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"},
}
},
},
}
},
}

124
tests/test_tutorial/test_header_params/test_tutorial002_py310.py

@ -1,124 +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.header_params.tutorial002_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"strange_header": None}),
("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}),
(
"/items",
{"strange_header": "FastAPI test"},
200,
{"strange_header": "FastAPI test"},
),
(
"/items",
{"strange-header": "Not really underscore"},
200,
{"strange_header": None},
),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema():
from docs_src.header_params.tutorial002_py310 import app
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Strange Header",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Strange Header", "type": "string"}
),
"name": "strange_header",
"in": "header",
}
],
}
}
},
"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"},
}
},
},
}
},
}

33
tests/test_tutorial/test_header_params/test_tutorial003.py

@ -1,10 +1,27 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.header_params.tutorial003 import app
from ...utils import needs_py39, needs_py310
@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.header_params.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client
@pytest.mark.parametrize(
@ -12,21 +29,17 @@ client = TestClient(app)
[
("/items", None, 200, {"X-Token values": None}),
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
(
"/items",
[("x-token", "foo"), ("x-token", "bar")],
200,
{"X-Token values": ["foo", "bar"]},
),
# TODO: fix this, is it a bug?
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
],
)
def test(path, headers, expected_status, expected_response):
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {

110
tests/test_tutorial/test_header_params/test_tutorial003_an.py

@ -1,110 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.header_params.tutorial003_an import app
client = TestClient(app)
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"X-Token values": None}),
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
# TODO: fix this, is it a bug?
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
],
)
def test(path, headers, expected_status, expected_response):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"title": "X-Token",
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "X-Token",
"type": "array",
"items": {"type": "string"},
}
),
"name": "x-token",
"in": "header",
}
],
"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"},
},
},
}
},
}

118
tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py

@ -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.header_params.tutorial003_an_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"X-Token values": None}),
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
# TODO: fix this, is it a bug?
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"title": "X-Token",
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "X-Token",
"type": "array",
"items": {"type": "string"},
}
),
"name": "x-token",
"in": "header",
}
],
"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"},
},
},
}
},
}

118
tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py

@ -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.header_params.tutorial003_an_py39 import app
client = TestClient(app)
return client
@needs_py39
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"X-Token values": None}),
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
# TODO: fix this, is it a bug?
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py39
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"title": "X-Token",
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "X-Token",
"type": "array",
"items": {"type": "string"},
}
),
"name": "x-token",
"in": "header",
}
],
"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"},
},
},
}
},
}

118
tests/test_tutorial/test_header_params/test_tutorial003_py310.py

@ -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.header_params.tutorial003_py310 import app
client = TestClient(app)
return client
@needs_py310
@pytest.mark.parametrize(
"path,headers,expected_status,expected_response",
[
("/items", None, 200, {"X-Token values": None}),
("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}),
# TODO: fix this, is it a bug?
# ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}),
],
)
def test(path, headers, expected_status, expected_response, client: TestClient):
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": IsDict(
{
"title": "X-Token",
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "X-Token",
"type": "array",
"items": {"type": "string"},
}
),
"name": "x-token",
"in": "header",
}
],
"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"},
},
},
}
},
}

28
tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py

@ -1,13 +1,29 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from docs_src.path_operation_configuration.tutorial005 import app
from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
from ...utils import needs_pydanticv1, needs_pydanticv2
@pytest.fixture(
name="client",
params=[
"tutorial005",
pytest.param("tutorial005_py39", marks=needs_py39),
pytest.param("tutorial005_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(
f"docs_src.path_operation_configuration.{request.param}"
)
client = TestClient(app)
client = TestClient(mod.app)
return client
def test_query_params_str_validations():
def test_query_params_str_validations(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": 42})
assert response.status_code == 200, response.text
assert response.json() == {
@ -20,7 +36,7 @@ def test_query_params_str_validations():
@needs_pydanticv2
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
@ -123,7 +139,7 @@ def test_openapi_schema():
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_openapi_schema_pv1():
def test_openapi_schema_pv1(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {

226
tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py

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

226
tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py

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

18
tests/test_tutorial/test_query_params/test_tutorial006.py

@ -1,13 +1,23 @@
import importlib
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.tutorial006 import app
@pytest.fixture(
name="client",
params=[
"tutorial006",
pytest.param("tutorial006_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.query_params.{request.param}")
c = TestClient(app)
c = TestClient(mod.app)
return c

180
tests/test_tutorial/test_query_params/test_tutorial006_py310.py

@ -1,180 +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.tutorial006_py310 import app
c = TestClient(app)
return c
@needs_py310
def test_foo_needy_very(client: TestClient):
response = client.get("/items/foo?needy=very")
assert response.status_code == 200
assert response.json() == {
"item_id": "foo",
"needy": "very",
"skip": 0,
"limit": None,
}
@needs_py310
def test_foo_no_needy(client: TestClient):
response = client.get("/items/foo?skip=a&limit=b")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "needy"],
"msg": "Field required",
"input": None,
},
{
"type": "int_parsing",
"loc": ["query", "skip"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "a",
},
{
"type": "int_parsing",
"loc": ["query", "limit"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "b",
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "needy"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["query", "skip"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
},
{
"loc": ["query", "limit"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
},
]
}
)
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read User Item",
"operationId": "read_user_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
{
"required": True,
"schema": {"title": "Needy", "type": "string"},
"name": "needy",
"in": "query",
},
{
"required": False,
"schema": {
"title": "Skip",
"type": "integer",
"default": 0,
},
"name": "skip",
"in": "query",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "integer"}, {"type": "null"}],
"title": "Limit",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Limit", "type": "integer"}
),
"name": "limit",
"in": "query",
},
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

19
tests/test_tutorial/test_request_form_models/test_tutorial001.py

@ -1,13 +1,24 @@
import importlib
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_form_models.tutorial001 import 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_form_models.{request.param}")
client = TestClient(app)
client = TestClient(mod.app)
return client

232
tests/test_tutorial/test_request_form_models/test_tutorial001_an.py

@ -1,232 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
@pytest.fixture(name="client")
def get_client():
from docs_src.request_form_models.tutorial001_an import app
client = TestClient(app)
return client
def test_post_body_form(client: TestClient):
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
assert response.status_code == 200
assert response.json() == {"username": "Foo", "password": "secret"}
def test_post_body_form_no_password(client: TestClient):
response = client.post("/login/", data={"username": "Foo"})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["body", "password"],
"msg": "Field required",
"input": {"username": "Foo"},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "password"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
def test_post_body_form_no_username(client: TestClient):
response = client.post("/login/", data={"password": "secret"})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["body", "username"],
"msg": "Field required",
"input": {"password": "secret"},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "username"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
def test_post_body_form_no_data(client: TestClient):
response = client.post("/login/")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["body", "username"],
"msg": "Field required",
"input": {},
},
{
"type": "missing",
"loc": ["body", "password"],
"msg": "Field required",
"input": {},
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "username"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "password"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_post_body_json(client: TestClient):
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["body", "username"],
"msg": "Field required",
"input": {},
},
{
"type": "missing",
"loc": ["body", "password"],
"msg": "Field required",
"input": {},
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "username"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "password"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/login/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Login",
"operationId": "login_login__post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {"$ref": "#/components/schemas/FormData"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"FormData": {
"properties": {
"username": {"type": "string", "title": "Username"},
"password": {"type": "string", "title": "Password"},
},
"type": "object",
"required": ["username", "password"],
"title": "FormData",
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

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

Loading…
Cancel
Save