diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/DISCUSSION_TEMPLATE/questions.yml
similarity index 87%
rename from .github/ISSUE_TEMPLATE/question.yml
rename to .github/DISCUSSION_TEMPLATE/questions.yml
index 3b16b4ad0..3726b7d18 100644
--- a/.github/ISSUE_TEMPLATE/question.yml
+++ b/.github/DISCUSSION_TEMPLATE/questions.yml
@@ -1,5 +1,3 @@
-name: Question or Problem
-description: Ask a question or ask about a problem
labels: [question]
body:
- type: markdown
@@ -9,9 +7,9 @@ body:
Please follow these instructions, fill every question, and do every step. 🙏
- I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
+ I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time.
- I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
+ I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions.
All that, on top of all the incredible help provided by a bunch of community members, the [FastAPI Experts](https://fastapi.tiangolo.com/fastapi-people/#experts), that give a lot of their time to come here and help others.
@@ -21,16 +19,16 @@ body:
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
- As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
+ As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- - label: I added a very descriptive title to this issue.
+ - label: I added a very descriptive title here.
required: true
- - label: I used the GitHub search to find a similar issue and didn't find it.
+ - label: I used the GitHub search to find a similar question and didn't find it.
required: true
- label: I searched the FastAPI documentation, with the integrated search.
required: true
@@ -38,7 +36,7 @@ body:
required: true
- label: I already read and followed all the tutorial in the docs and didn't find an answer.
required: true
- - label: I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
+ - label: I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/pydantic/pydantic).
required: true
- label: I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
required: true
@@ -51,9 +49,9 @@ body:
description: |
After submitting this, I commit to one of:
- * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
+ * Read open questions until I find 2 where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
- * Implement a Pull Request for a confirmed bug.
+ * Review one Pull Request by downloading the code and following [all the review process](https://fastapi.tiangolo.com/help-fastapi/#review-pull-requests).
options:
- label: I commit to help with one of those options 👆
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 55749398f..a8f4c4de2 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -2,3 +2,15 @@ blank_issues_enabled: false
contact_links:
- name: Security Contact
about: Please report security vulnerabilities to security@tiangolo.com
+ - name: Question or Problem
+ about: Ask a question or ask about a problem in GitHub Discussions.
+ url: https://github.com/tiangolo/fastapi/discussions/categories/questions
+ - name: Feature Request
+ about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already.
+ url: https://github.com/tiangolo/fastapi/discussions/categories/questions
+ - name: Show and tell
+ about: Show what you built with FastAPI or to be used with FastAPI.
+ url: https://github.com/tiangolo/fastapi/discussions/categories/show-and-tell
+ - name: Translations
+ about: Coordinate translations in GitHub Discussions.
+ url: https://github.com/tiangolo/fastapi/discussions/categories/translations
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
deleted file mode 100644
index 322b6536a..000000000
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ /dev/null
@@ -1,181 +0,0 @@
-name: Feature Request
-description: Suggest an idea or ask for a feature that you would like to have in FastAPI
-labels: [enhancement]
-body:
- - type: markdown
- attributes:
- value: |
- Thanks for your interest in FastAPI! 🚀
-
- Please follow these instructions, fill every question, and do every step. 🙏
-
- I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
-
- I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
-
- All that, on top of all the incredible help provided by a bunch of community members, the [FastAPI Experts](https://fastapi.tiangolo.com/fastapi-people/#experts), that give a lot of their time to come here and help others.
-
- That's a lot of work they are doing, but if more FastAPI users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
-
- By asking questions in a structured way (following this) it will be much easier to help you.
-
- And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
-
- As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- - type: checkboxes
- id: checks
- attributes:
- label: First Check
- description: Please confirm and check all the following options.
- options:
- - label: I added a very descriptive title to this issue.
- required: true
- - label: I used the GitHub search to find a similar issue and didn't find it.
- required: true
- - label: I searched the FastAPI documentation, with the integrated search.
- required: true
- - label: I already searched in Google "How to X in FastAPI" and didn't find any information.
- required: true
- - label: I already read and followed all the tutorial in the docs and didn't find an answer.
- required: true
- - label: I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
- required: true
- - label: I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
- required: true
- - label: I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
- required: true
- - type: checkboxes
- id: help
- attributes:
- label: Commit to Help
- description: |
- After submitting this, I commit to one of:
-
- * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
- * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
- * Implement a Pull Request for a confirmed bug.
-
- options:
- - label: I commit to help with one of those options 👆
- required: true
- - type: textarea
- id: example
- attributes:
- label: Example Code
- description: |
- Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
-
- If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
-
- placeholder: |
- from fastapi import FastAPI
-
- app = FastAPI()
-
-
- @app.get("/")
- def read_root():
- return {"Hello": "World"}
- render: python
- validations:
- required: true
- - type: textarea
- id: description
- attributes:
- label: Description
- description: |
- What is your feature request?
-
- Write a short description telling me what you are trying to solve and what you are currently doing.
- placeholder: |
- * Open the browser and call the endpoint `/`.
- * It returns a JSON with `{"Hello": "World"}`.
- * I would like it to have an extra parameter to teleport me to the moon and back.
- validations:
- required: true
- - type: textarea
- id: wanted-solution
- attributes:
- label: Wanted Solution
- description: |
- Tell me what's the solution you would like.
- placeholder: |
- I would like it to have a `teleport_to_moon` parameter that defaults to `False`, and can be set to `True` to teleport me.
- validations:
- required: true
- - type: textarea
- id: wanted-code
- attributes:
- label: Wanted Code
- description: Show me an example of how you would want the code to look like.
- placeholder: |
- from fastapi import FastAPI
-
- app = FastAPI()
-
-
- @app.get("/", teleport_to_moon=True)
- def read_root():
- return {"Hello": "World"}
- render: python
- validations:
- required: true
- - type: textarea
- id: alternatives
- attributes:
- label: Alternatives
- description: |
- Tell me about alternatives you've considered.
- placeholder: |
- To wait for Space X moon travel plans to drop down long after they release them. But I would rather teleport.
- - type: dropdown
- id: os
- attributes:
- label: Operating System
- description: What operating system are you on?
- multiple: true
- options:
- - Linux
- - Windows
- - macOS
- - Other
- validations:
- required: true
- - type: textarea
- id: os-details
- attributes:
- label: Operating System Details
- description: You can add more details about your operating system here, in particular if you chose "Other".
- - type: input
- id: fastapi-version
- attributes:
- label: FastAPI Version
- description: |
- What FastAPI version are you using?
-
- You can find the FastAPI version with:
-
- ```bash
- python -c "import fastapi; print(fastapi.__version__)"
- ```
- validations:
- required: true
- - type: input
- id: python-version
- attributes:
- label: Python Version
- description: |
- What Python version are you using?
-
- You can find the Python version with:
-
- ```bash
- python --version
- ```
- validations:
- required: true
- - type: textarea
- id: context
- attributes:
- label: Additional Context
- description: Add any additional context information or screenshots you think are useful.
diff --git a/.github/ISSUE_TEMPLATE/privileged.yml b/.github/ISSUE_TEMPLATE/privileged.yml
new file mode 100644
index 000000000..c01e34b6d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/privileged.yml
@@ -0,0 +1,22 @@
+name: Privileged
+description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for your interest in FastAPI! 🚀
+
+ If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/fastapi/discussions/categories/questions) instead.
+ - type: checkboxes
+ id: privileged
+ attributes:
+ label: Privileged issue
+ description: Confirm that you are allowed to create an issue here.
+ options:
+ - label: I'm @tiangolo or he asked me directly to create an issue here.
+ required: true
+ - type: textarea
+ id: content
+ attributes:
+ label: Issue Content
+ description: Add the content of the issue here.
diff --git a/.github/actions/notify-translations/app/main.py b/.github/actions/notify-translations/app/main.py
index d4ba0ecfc..494fe6ad8 100644
--- a/.github/actions/notify-translations/app/main.py
+++ b/.github/actions/notify-translations/app/main.py
@@ -1,10 +1,11 @@
import logging
import random
+import sys
import time
from pathlib import Path
-from typing import Dict, Union
+from typing import Any, Dict, List, Union, cast
-import yaml
+import httpx
from github import Github
from pydantic import BaseModel, BaseSettings, SecretStr
@@ -13,12 +14,172 @@ lang_all_label = "lang-all"
approved_label = "approved-2"
translations_path = Path(__file__).parent / "translations.yml"
+github_graphql_url = "https://api.github.com/graphql"
+questions_translations_category_id = "DIC_kwDOCZduT84CT5P9"
+
+all_discussions_query = """
+query Q($category_id: ID) {
+ repository(name: "fastapi", owner: "tiangolo") {
+ discussions(categoryId: $category_id, first: 100) {
+ nodes {
+ title
+ id
+ number
+ labels(first: 10) {
+ edges {
+ node {
+ id
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+}
+"""
+
+translation_discussion_query = """
+query Q($after: String, $discussion_number: Int!) {
+ repository(name: "fastapi", owner: "tiangolo") {
+ discussion(number: $discussion_number) {
+ comments(first: 100, after: $after) {
+ edges {
+ cursor
+ node {
+ id
+ url
+ body
+ }
+ }
+ }
+ }
+ }
+}
+"""
+
+add_comment_mutation = """
+mutation Q($discussion_id: ID!, $body: String!) {
+ addDiscussionComment(input: {discussionId: $discussion_id, body: $body}) {
+ comment {
+ id
+ url
+ body
+ }
+ }
+}
+"""
+
+update_comment_mutation = """
+mutation Q($comment_id: ID!, $body: String!) {
+ updateDiscussionComment(input: {commentId: $comment_id, body: $body}) {
+ comment {
+ id
+ url
+ body
+ }
+ }
+}
+"""
+
+
+class Comment(BaseModel):
+ id: str
+ url: str
+ body: str
+
+
+class UpdateDiscussionComment(BaseModel):
+ comment: Comment
+
+
+class UpdateCommentData(BaseModel):
+ updateDiscussionComment: UpdateDiscussionComment
+
+
+class UpdateCommentResponse(BaseModel):
+ data: UpdateCommentData
+
+
+class AddDiscussionComment(BaseModel):
+ comment: Comment
+
+
+class AddCommentData(BaseModel):
+ addDiscussionComment: AddDiscussionComment
+
+
+class AddCommentResponse(BaseModel):
+ data: AddCommentData
+
+
+class CommentsEdge(BaseModel):
+ node: Comment
+ cursor: str
+
+
+class Comments(BaseModel):
+ edges: List[CommentsEdge]
+
+
+class CommentsDiscussion(BaseModel):
+ comments: Comments
+
+
+class CommentsRepository(BaseModel):
+ discussion: CommentsDiscussion
+
+
+class CommentsData(BaseModel):
+ repository: CommentsRepository
+
+
+class CommentsResponse(BaseModel):
+ data: CommentsData
+
+
+class AllDiscussionsLabelNode(BaseModel):
+ id: str
+ name: str
+
+
+class AllDiscussionsLabelsEdge(BaseModel):
+ node: AllDiscussionsLabelNode
+
+
+class AllDiscussionsDiscussionLabels(BaseModel):
+ edges: List[AllDiscussionsLabelsEdge]
+
+
+class AllDiscussionsDiscussionNode(BaseModel):
+ title: str
+ id: str
+ number: int
+ labels: AllDiscussionsDiscussionLabels
+
+
+class AllDiscussionsDiscussions(BaseModel):
+ nodes: List[AllDiscussionsDiscussionNode]
+
+
+class AllDiscussionsRepository(BaseModel):
+ discussions: AllDiscussionsDiscussions
+
+
+class AllDiscussionsData(BaseModel):
+ repository: AllDiscussionsRepository
+
+
+class AllDiscussionsResponse(BaseModel):
+ data: AllDiscussionsData
+
class Settings(BaseSettings):
github_repository: str
input_token: SecretStr
github_event_path: Path
github_event_name: Union[str, None] = None
+ httpx_timeout: int = 30
input_debug: Union[bool, None] = False
@@ -30,6 +191,113 @@ class PartialGitHubEvent(BaseModel):
pull_request: PartialGitHubEventIssue
+def get_graphql_response(
+ *,
+ settings: Settings,
+ query: str,
+ after: Union[str, None] = None,
+ category_id: Union[str, None] = None,
+ discussion_number: Union[int, None] = None,
+ discussion_id: Union[str, None] = None,
+ 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
+ variables = {
+ "after": after,
+ "category_id": category_id,
+ "discussion_number": discussion_number,
+ "discussion_id": discussion_id,
+ "comment_id": comment_id,
+ "body": body,
+ }
+ 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(response.text)
+ raise RuntimeError(response.text)
+ return cast(Dict[str, Any], data)
+
+
+def get_graphql_translation_discussions(*, settings: Settings):
+ data = get_graphql_response(
+ settings=settings,
+ query=all_discussions_query,
+ category_id=questions_translations_category_id,
+ )
+ graphql_response = AllDiscussionsResponse.parse_obj(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
+):
+ data = get_graphql_response(
+ settings=settings,
+ query=translation_discussion_query,
+ discussion_number=discussion_number,
+ after=after,
+ )
+ graphql_response = CommentsResponse.parse_obj(data)
+ return graphql_response.data.repository.discussion.comments.edges
+
+
+def get_graphql_translation_discussion_comments(
+ *, settings: Settings, discussion_number: int
+):
+ comment_nodes: List[Comment] = []
+ discussion_edges = get_graphql_translation_discussion_comments_edges(
+ settings=settings, discussion_number=discussion_number
+ )
+
+ while discussion_edges:
+ for discussion_edge in discussion_edges:
+ comment_nodes.append(discussion_edge.node)
+ last_edge = discussion_edges[-1]
+ discussion_edges = get_graphql_translation_discussion_comments_edges(
+ settings=settings,
+ discussion_number=discussion_number,
+ after=last_edge.cursor,
+ )
+ return comment_nodes
+
+
+def create_comment(*, settings: Settings, discussion_id: str, body: str):
+ data = get_graphql_response(
+ settings=settings,
+ query=add_comment_mutation,
+ discussion_id=discussion_id,
+ body=body,
+ )
+ response = AddCommentResponse.parse_obj(data)
+ return response.data.addDiscussionComment.comment
+
+
+def update_comment(*, settings: Settings, comment_id: str, body: str):
+ data = get_graphql_response(
+ settings=settings,
+ query=update_comment_mutation,
+ comment_id=comment_id,
+ body=body,
+ )
+ response = UpdateCommentResponse.parse_obj(data)
+ return response.data.updateDiscussionComment.comment
+
+
if __name__ == "__main__":
settings = Settings()
if settings.input_debug:
@@ -45,60 +313,105 @@ if __name__ == "__main__":
)
contents = settings.github_event_path.read_text()
github_event = PartialGitHubEvent.parse_raw(contents)
- translations_map: Dict[str, int] = yaml.safe_load(translations_path.read_text())
- logging.debug(f"Using translations map: {translations_map}")
+
+ # Avoid race conditions with multiple labels
sleep_time = random.random() * 10 # random number between 0 and 10 seconds
- pr = repo.get_pull(github_event.pull_request.number)
- logging.debug(
- f"Processing PR: {pr.number}, with anti-race condition sleep time: {sleep_time}"
+ logging.info(
+ f"Sleeping for {sleep_time} seconds to avoid "
+ "race conditions and multiple comments"
)
- if pr.state == "open":
- logging.debug(f"PR is open: {pr.number}")
- label_strs = {label.name for label in pr.get_labels()}
- if lang_all_label in label_strs and awaiting_label in label_strs:
+ 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)
+ label_strs = {label.name for label in pr.get_labels()}
+ langs = []
+ for label in label_strs:
+ if label.startswith("lang-") and not label == lang_all_label:
+ langs.append(label[5:])
+ logging.info(f"PR #{pr.number} has labels: {label_strs}")
+ if not langs or lang_all_label not in label_strs:
+ logging.info(f"PR #{pr.number} doesn't seem to be a translation PR, skipping")
+ sys.exit(0)
+
+ # Generate translation map, lang ID to discussion
+ discussions = get_graphql_translation_discussions(settings=settings)
+ lang_to_discussion_map: Dict[str, AllDiscussionsDiscussionNode] = {}
+ for discussion in discussions:
+ for edge in discussion.labels.edges:
+ label = edge.node.name
+ if label.startswith("lang-") and not label == lang_all_label:
+ lang = label[5:]
+ lang_to_discussion_map[lang] = discussion
+ logging.debug(f"Using translations map: {lang_to_discussion_map}")
+
+ # Messages to create or check
+ new_translation_message = f"Good news everyone! 😉 There's a new translation PR to be reviewed: #{pr.number} by @{pr.user.login}. 🎉 This requires 2 approvals from native speakers to be merged. 🤓"
+ done_translation_message = f"~There's a new translation PR to be reviewed: #{pr.number} by @{pr.user.login}~ Good job! This is done. 🍰☕"
+
+ # Normally only one language, but still
+ for lang in langs:
+ if lang not in lang_to_discussion_map:
+ log_message = f"Could not find discussion for language: {lang}"
+ logging.error(log_message)
+ raise RuntimeError(log_message)
+ discussion = lang_to_discussion_map[lang]
+ logging.info(
+ f"Found a translation discussion for language: {lang} in discussion: #{discussion.number}"
+ )
+
+ already_notified_comment: Union[Comment, None] = None
+ already_done_comment: Union[Comment, None] = None
+
+ logging.info(
+ f"Checking current comments in discussion: #{discussion.number} to see if already notified about this PR: #{pr.number}"
+ )
+ comments = get_graphql_translation_discussion_comments(
+ settings=settings, discussion_number=discussion.number
+ )
+ for comment in comments:
+ if new_translation_message in comment.body:
+ already_notified_comment = comment
+ elif done_translation_message in comment.body:
+ already_done_comment = comment
+ logging.info(
+ f"Already notified comment: {already_notified_comment}, already done comment: {already_done_comment}"
+ )
+
+ if pr.state == "open" and awaiting_label in label_strs:
logging.info(
- f"This PR seems to be a language translation and awaiting reviews: {pr.number}"
+ f"This PR seems to be a language translation and awaiting reviews: #{pr.number}"
)
- if approved_label in label_strs:
- message = (
- f"It seems this PR already has the approved label: {pr.number}"
+ if already_notified_comment:
+ logging.info(
+ f"This PR #{pr.number} was already notified in comment: {already_notified_comment.url}"
)
- logging.error(message)
- raise RuntimeError(message)
- langs = []
- for label in label_strs:
- if label.startswith("lang-") and not label == lang_all_label:
- langs.append(label[5:])
- for lang in langs:
- if lang in translations_map:
- num = translations_map[lang]
- logging.info(
- f"Found a translation issue for language: {lang} in issue: {num}"
- )
- issue = repo.get_issue(num)
- message = f"Good news everyone! 😉 There's a new translation PR to be reviewed: #{pr.number} 🎉"
- already_notified = False
- time.sleep(sleep_time)
- logging.info(
- f"Sleeping for {sleep_time} seconds to avoid race conditions and multiple comments"
- )
- logging.info(
- f"Checking current comments in issue: {num} to see if already notified about this PR: {pr.number}"
- )
- for comment in issue.get_comments():
- if message in comment.body:
- already_notified = True
- if not already_notified:
- logging.info(
- f"Writing comment in issue: {num} about PR: {pr.number}"
- )
- issue.create_comment(message)
- else:
- logging.info(
- f"Issue: {num} was already notified of PR: {pr.number}"
- )
- else:
- logging.info(
- f"Changing labels in a closed PR doesn't trigger comments, PR: {pr.number}"
- )
+ else:
+ logging.info(
+ f"Writing notification comment about PR #{pr.number} in Discussion: #{discussion.number}"
+ )
+ comment = create_comment(
+ settings=settings,
+ discussion_id=discussion.id,
+ body=new_translation_message,
+ )
+ logging.info(f"Notified in comment: {comment.url}")
+ elif pr.state == "closed" or approved_label in label_strs:
+ logging.info(f"Already approved or closed PR #{pr.number}")
+ if already_done_comment:
+ logging.info(
+ f"This PR #{pr.number} was already marked as done in comment: {already_done_comment.url}"
+ )
+ elif already_notified_comment:
+ updated_comment = update_comment(
+ settings=settings,
+ comment_id=already_notified_comment.id,
+ body=done_translation_message,
+ )
+ logging.info(f"Marked as done in comment: {updated_comment.url}")
+ else:
+ logging.info(
+ f"There doesn't seem to be anything to be done about PR #{pr.number}"
+ )
logging.info("Finished")
diff --git a/.github/actions/notify-translations/app/translations.yml b/.github/actions/notify-translations/app/translations.yml
deleted file mode 100644
index 4338e1326..000000000
--- a/.github/actions/notify-translations/app/translations.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-pt: 1211
-es: 1218
-zh: 1228
-ru: 1362
-it: 1556
-ja: 1572
-uk: 1748
-tr: 1892
-fr: 1972
-ko: 2017
-fa: 2041
-pl: 3169
-de: 3716
-id: 3717
-az: 3994
-nl: 4701
-uz: 4883
-sv: 5146
-he: 5157
-ta: 5434
-ar: 3349
diff --git a/.github/actions/people/app/main.py b/.github/actions/people/app/main.py
index 31756a5fc..2bf59f25e 100644
--- a/.github/actions/people/app/main.py
+++ b/.github/actions/people/app/main.py
@@ -4,7 +4,7 @@ import sys
from collections import Counter, defaultdict
from datetime import datetime, timedelta, timezone
from pathlib import Path
-from typing import Container, DefaultDict, Dict, List, Set, Union
+from typing import Any, Container, DefaultDict, Dict, List, Set, Union
import httpx
import yaml
@@ -12,6 +12,50 @@ from github import Github
from pydantic import BaseModel, BaseSettings, SecretStr
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: "tiangolo") {
+ 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
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+"""
issues_query = """
query Q($after: String) {
@@ -131,15 +175,30 @@ class Author(BaseModel):
url: str
+# Issues and 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 IssuesNode(BaseModel):
number: int
author: Union[Author, None] = None
@@ -149,27 +208,59 @@ class IssuesNode(BaseModel):
comments: Comments
+class DiscussionsNode(BaseModel):
+ number: int
+ author: Union[Author, None] = None
+ title: str
+ createdAt: datetime
+ comments: DiscussionsComments
+
+
class IssuesEdge(BaseModel):
cursor: str
node: IssuesNode
+class DiscussionsEdge(BaseModel):
+ cursor: str
+ node: DiscussionsNode
+
+
class Issues(BaseModel):
edges: List[IssuesEdge]
+class Discussions(BaseModel):
+ edges: List[DiscussionsEdge]
+
+
class IssuesRepository(BaseModel):
issues: Issues
+class DiscussionsRepository(BaseModel):
+ discussions: Discussions
+
+
class IssuesResponseData(BaseModel):
repository: IssuesRepository
+class DiscussionsResponseData(BaseModel):
+ repository: DiscussionsRepository
+
+
class IssuesResponse(BaseModel):
data: IssuesResponseData
+class DiscussionsResponse(BaseModel):
+ data: DiscussionsResponseData
+
+
+# PRs
+
+
class LabelNode(BaseModel):
name: str
@@ -219,6 +310,9 @@ class PRsResponse(BaseModel):
data: PRsResponseData
+# Sponsors
+
+
class SponsorEntity(BaseModel):
login: str
avatarUrl: str
@@ -264,10 +358,16 @@ class Settings(BaseSettings):
def get_graphql_response(
- *, settings: Settings, query: str, after: Union[str, None] = None
-):
+ *,
+ 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()}"}
- variables = {"after": after}
+ # 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,
@@ -275,10 +375,16 @@ def get_graphql_response(
json={"query": query, "variables": variables, "operationName": "Q"},
)
if response.status_code != 200:
- logging.error(f"Response was not 200, after: {after}")
+ 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(response.text)
+ raise RuntimeError(response.text)
return data
@@ -288,6 +394,21 @@ def get_graphql_issue_edges(*, settings: Settings, after: Union[str, None] = Non
return graphql_response.data.repository.issues.edges
+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.parse_obj(data)
+ return graphql_response.data.repository.discussions.edges
+
+
def get_graphql_pr_edges(*, settings: Settings, after: Union[str, None] = None):
data = get_graphql_response(settings=settings, query=prs_query, after=after)
graphql_response = PRsResponse.parse_obj(data)
@@ -300,7 +421,7 @@ def get_graphql_sponsor_edges(*, settings: Settings, after: Union[str, None] = N
return graphql_response.data.user.sponsorshipsAsMaintainer.edges
-def get_experts(settings: Settings):
+def get_issues_experts(settings: Settings):
issue_nodes: List[IssuesNode] = []
issue_edges = get_graphql_issue_edges(settings=settings)
@@ -326,13 +447,78 @@ def get_experts(settings: Settings):
for comment in issue.comments.nodes:
if comment.author:
authors[comment.author.login] = comment.author
- if comment.author.login == issue_author_name:
- continue
- issue_commentors.add(comment.author.login)
+ if comment.author.login != issue_author_name:
+ issue_commentors.add(comment.author.login)
for author_name in issue_commentors:
commentors[author_name] += 1
if issue.createdAt > one_month_ago:
last_month_commentors[author_name] += 1
+
+ return commentors, last_month_commentors, authors
+
+
+def get_discussions_experts(settings: Settings):
+ 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
+ )
+
+ commentors = Counter()
+ last_month_commentors = Counter()
+ authors: Dict[str, Author] = {}
+
+ now = datetime.now(tz=timezone.utc)
+ one_month_ago = now - timedelta(days=30)
+
+ 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 = set()
+ for comment in discussion.comments.nodes:
+ if comment.author:
+ authors[comment.author.login] = comment.author
+ if comment.author.login != discussion_author_name:
+ discussion_commentors.add(comment.author.login)
+ for reply in comment.replies.nodes:
+ if reply.author:
+ authors[reply.author.login] = reply.author
+ if reply.author.login != discussion_author_name:
+ discussion_commentors.add(reply.author.login)
+ for author_name in discussion_commentors:
+ commentors[author_name] += 1
+ if discussion.createdAt > one_month_ago:
+ last_month_commentors[author_name] += 1
+ return commentors, last_month_commentors, authors
+
+
+def get_experts(settings: Settings):
+ # Migrated to only use GitHub Discussions
+ # (
+ # issues_commentors,
+ # issues_last_month_commentors,
+ # issues_authors,
+ # ) = get_issues_experts(settings=settings)
+ (
+ discussions_commentors,
+ discussions_last_month_commentors,
+ discussions_authors,
+ ) = get_discussions_experts(settings=settings)
+ # commentors = issues_commentors + discussions_commentors
+ commentors = discussions_commentors
+ # last_month_commentors = (
+ # issues_last_month_commentors + discussions_last_month_commentors
+ # )
+ last_month_commentors = discussions_last_month_commentors
+ # authors = {**issues_authors, **discussions_authors}
+ authors = {**discussions_authors}
return commentors, last_month_commentors, authors
@@ -425,13 +611,13 @@ if __name__ == "__main__":
logging.info(f"Using config: {settings.json()}")
g = Github(settings.input_standard_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
- issue_commentors, issue_last_month_commentors, issue_authors = get_experts(
+ question_commentors, question_last_month_commentors, question_authors = get_experts(
settings=settings
)
contributors, pr_commentors, reviewers, pr_authors = get_contributors(
settings=settings
)
- authors = {**issue_authors, **pr_authors}
+ authors = {**question_authors, **pr_authors}
maintainers_logins = {"tiangolo"}
bot_names = {"codecov", "github-actions", "pre-commit-ci", "dependabot"}
maintainers = []
@@ -440,7 +626,7 @@ if __name__ == "__main__":
maintainers.append(
{
"login": login,
- "answers": issue_commentors[login],
+ "answers": question_commentors[login],
"prs": contributors[login],
"avatarUrl": user.avatarUrl,
"url": user.url,
@@ -453,13 +639,13 @@ if __name__ == "__main__":
min_count_reviewer = 4
skip_users = maintainers_logins | bot_names
experts = get_top_users(
- counter=issue_commentors,
+ counter=question_commentors,
min_count=min_count_expert,
authors=authors,
skip_users=skip_users,
)
last_month_active = get_top_users(
- counter=issue_last_month_commentors,
+ counter=question_last_month_commentors,
min_count=min_count_last_month,
authors=authors,
skip_users=skip_users,
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index b9bd521b3..68a180e38 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -7,7 +7,7 @@ on:
types: [opened, synchronize]
jobs:
build-docs:
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
@@ -17,7 +17,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
- python-version: "3.7"
+ python-version: "3.11"
- uses: actions/cache@v3
id: cache
with:
diff --git a/.github/workflows/notify-translations.yml b/.github/workflows/notify-translations.yml
index 2fcb7595e..fdd24414c 100644
--- a/.github/workflows/notify-translations.yml
+++ b/.github/workflows/notify-translations.yml
@@ -4,6 +4,7 @@ on:
pull_request_target:
types:
- labeled
+ - closed
jobs:
notify-translations:
diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml
index 4b47b4072..cca1329e7 100644
--- a/.github/workflows/people.yml
+++ b/.github/workflows/people.yml
@@ -15,6 +15,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
+ # 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
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml
index 7d31a9c64..cf0db59ab 100644
--- a/.github/workflows/preview-docs.yml
+++ b/.github/workflows/preview-docs.yml
@@ -16,7 +16,7 @@ jobs:
rm -rf ./site
mkdir ./site
- name: Download Artifact Docs
- uses: dawidd6/action-download-artifact@v2.24.2
+ uses: dawidd6/action-download-artifact@v2.26.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build-docs.yml
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 8ffb493a4..c2fdb8e17 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -31,7 +31,7 @@ jobs:
- name: Build distribution
run: python -m build
- name: Publish
- uses: pypa/gh-action-pypi-publish@v1.5.2
+ uses: pypa/gh-action-pypi-publish@v1.6.4
with:
password: ${{ secrets.PYPI_API_TOKEN }}
- name: Dump GitHub context
diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml
index 7559c24c0..421720433 100644
--- a/.github/workflows/smokeshow.yml
+++ b/.github/workflows/smokeshow.yml
@@ -20,7 +20,7 @@ jobs:
- run: pip install smokeshow
- - uses: dawidd6/action-download-artifact@v2.24.2
+ - uses: dawidd6/action-download-artifact@v2.26.0
with:
workflow: test.yml
commit: ${{ github.event.workflow_run.head_sha }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ddc43c942..1235516d3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -75,3 +75,19 @@ jobs:
with:
name: coverage-html
path: htmlcov
+
+ # https://github.com/marketplace/actions/alls-green#why
+ check: # This job does nothing and is only used for the branch protection
+
+ if: always()
+
+ needs:
+ - coverage-combine
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Decide whether the needed jobs succeeded or failed
+ uses: re-actors/alls-green@release/v1
+ with:
+ jobs: ${{ toJSON(needs) }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 96f097caa..25e797d24 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,8 +1,10 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
+default_language_version:
+ python: python3.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
+ rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-toml
@@ -12,20 +14,20 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
- rev: v3.2.2
+ rev: v3.3.1
hooks:
- id: pyupgrade
args:
- --py3-plus
- --keep-runtime-typing
- repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: v0.0.138
+ rev: v0.0.254
hooks:
- id: ruff
args:
- --fix
- repo: https://github.com/pycqa/isort
- rev: 5.10.1
+ rev: 5.12.0
hooks:
- id: isort
name: isort (python)
@@ -36,7 +38,7 @@ repos:
name: isort (pyi)
types: [pyi]
- repo: https://github.com/psf/black
- rev: 22.10.0
+ rev: 23.1.0
hooks:
- id: black
ci:
diff --git a/README.md b/README.md
index 7c4a6c4b4..39030ef52 100644
--- a/README.md
+++ b/README.md
@@ -49,14 +49,14 @@ The key features are:
-
-
+
+
@@ -102,6 +102,12 @@ The key features are:
---
+"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._"
+
+
lt
.
-```Python hl_lines="11"
-{!../../../docs_src/path_params_numeric_validations/tutorial006.py!}
-```
+=== "Python 3.9+"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial006_an.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial006.py!}
+ ```
## Recap
diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md
index 060e1d58a..6a5a507b9 100644
--- a/docs/en/docs/tutorial/query-params-str-validations.md
+++ b/docs/en/docs/tutorial/query-params-str-validations.md
@@ -4,16 +4,16 @@
Let's take this application as example:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/query_params_str_validations/tutorial001.py!}
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="7"
- {!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!}
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial001.py!}
```
The query parameter `q` is of type `Union[str, None]` (or `str | None` in Python 3.10), that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required.
@@ -27,39 +27,117 @@ The query parameter `q` is of type `Union[str, None]` (or `str | None` in Python
We are going to enforce that even though `q` is optional, whenever it is provided, **its length doesn't exceed 50 characters**.
-### Import `Query`
+### Import `Query` and `Annotated`
-To achieve that, first import `Query` from `fastapi`:
+To achieve that, first import:
-=== "Python 3.6 and above"
+* `Query` from `fastapi`
+* `Annotated` from `typing` (or from `typing_extensions` in Python below 3.9)
- ```Python hl_lines="3"
- {!> ../../../docs_src/query_params_str_validations/tutorial002.py!}
+=== "Python 3.10+"
+
+ In Python 3.9 or above, `Annotated` is part of the standard library, so you can import it from `typing`.
+
+ ```Python hl_lines="1 3"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_an_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="1"
- {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!}
+ In versions of Python below Python 3.9 you import `Annotation` from `typing_extensions`.
+
+ It will already be installed with FastAPI.
+
+ ```Python hl_lines="3-4"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_an.py!}
```
-## Use `Query` as the default value
+## Use `Annotated` in the type for the `q` parameter
+
+Remember I told you before that `Annotated` can be used to add metadata to your parameters in the [Python Types Intro](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
-And now use it as the default value of your parameter, setting the parameter `max_length` to 50:
+Now it's the time to use it with FastAPI. 🚀
+
+We had this type annotation:
+
+=== "Python 3.10+"
+
+ ```Python
+ q: str | None = None
+ ```
-=== "Python 3.6 and above"
+=== "Python 3.6+"
+
+ ```Python
+ q: Union[str, None] = None
+ ```
+
+What we will do is wrap that with `Annotated`, so it becomes:
+
+=== "Python 3.10+"
+
+ ```Python
+ q: Annotated[str | None] = None
+ ```
+
+=== "Python 3.6+"
+
+ ```Python
+ q: Annotated[Union[str, None]] = None
+ ```
+
+Both of those versions mean the same thing, `q` is a parameter that can be a `str` or `None`, and by default, it is `None`.
+
+Now let's jump to the fun stuff. 🎉
+
+## Add `Query` to `Annotated` in the `q` parameter
+
+Now that we have this `Annotated` where we can put more metadata, add `Query` to it, and set the parameter `max_length` to 50:
+
+=== "Python 3.10+"
```Python hl_lines="9"
- {!> ../../../docs_src/query_params_str_validations/tutorial002.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_an_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_an.py!}
+ ```
+
+Notice that the default value is still `None`, so the parameter is still optional.
+
+But now, having `Query(max_length=50)` inside of `Annotated`, we are telling FastAPI that we want it to extract this value from the query parameters (this would have been the default anyway 🤷) and that we want to have **additional validation** for this value (that's why we do this, to get the additional validation). 😎
+
+FastAPI wll now:
+
+* **Validate** the data making sure that the max length is 50 characters
+* Show a **clear error** for the client when the data is not valid
+* **Document** the parameter in the OpenAPI schema *path operation* (so it will show up in the **automatic docs UI**)
+
+## Alternative (old) `Query` as the default value
+
+Previous versions of FastAPI (before 0.95.0) required you to use `Query` as the default value of your parameter, instead of putting it in `Annotated`, there's a high chance that you will see code using it around, so I'll explain it to you.
+
+!!! tip
+ For new code and whenever possible, use `Annotated` as explained above. There are multiple advantages (explained below) and no disadvantages. 🍰
+
+This is how you would use `Query()` as the default value of your function parameter, setting the parameter `max_length` to 50:
+
+=== "Python 3.10+"
```Python hl_lines="7"
{!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!}
```
-As we have to replace the default value `None` in the function with `Query()`, we can now set the default value with the parameter `Query(default=None)`, it serves the same purpose of defining that default value.
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002.py!}
+ ```
+
+As in this case (without using `Annotated`) we have to replace the default value `None` in the function with `Query()`, we now need to set the default value with the parameter `Query(default=None)`, it serves the same purpose of defining that default value (at least for FastAPI).
So:
@@ -67,7 +145,7 @@ So:
q: Union[str, None] = Query(default=None)
```
-...makes the parameter optional, the same as:
+...makes the parameter optional, with a default value of `None`, the same as:
```Python
q: Union[str, None] = None
@@ -79,7 +157,7 @@ And in Python 3.10 and above:
q: str | None = Query(default=None)
```
-...makes the parameter optional, the same as:
+...makes the parameter optional, with a default value of `None`, the same as:
```Python
q: str | None = None
@@ -112,38 +190,124 @@ q: Union[str, None] = Query(default=None, max_length=50)
This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema *path operation*.
+### `Query` as the default value or in `Annotated`
+
+Have in mind that when using `Query` inside of `Annotated` you cannot use the `default` parameter for `Query`.
+
+Instead use the actual default value of the function parameter. Otherwise, it would be inconsistent.
+
+For example, this is not allowed:
+
+```Python
+q: Annotated[str Query(default="rick")] = "morty"
+```
+
+...because it's not clear if the default value should be `"rick"` or `"morty"`.
+
+So, you would use (preferably):
+
+```Python
+q: Annotated[str, Query()] = "rick"
+```
+
+...or in older code bases you will find:
+
+```Python
+q: str = Query(default="rick")
+```
+
+### Advantages of `Annotated`
+
+**Using `Annotated` is recommended** instead of the default value in function parameters, it is **better** for multiple reasons. 🤓
+
+The **default** value of the **function parameter** is the **actual default** value, that's more intuitive with Python in general. 😌
+
+You could **call** that same function in **other places** without FastAPI, and it would **work as expected**. If there's a **required** parameter (without a default value), your **editor** will let you know with an error, **Python** will also complain if you run it without passing the required parameter.
+
+When you don't use `Annotated` and instead use the **(old) default value style**, if you call that function without FastAPI in **other place**, you have to **remember** to pass the arguments to the function for it to work correctly, otherwise the values will be different from what you expect (e.g. `QueryInfo` or something similar instead of `str`). And your editor won't complain, and Python won't complain running that function, only when the operations inside error out.
+
+Because `Annotated` can have more than one metadata annotation, you could now even use the same function with other tools, like Typer. 🚀
+
## Add more validations
You can also add a parameter `min_length`:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
```Python hl_lines="10"
- {!> ../../../docs_src/query_params_str_validations/tutorial003.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial003_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial003_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/query_params_str_validations/tutorial003_an.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="7"
{!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial003.py!}
+ ```
+
## Add regular expressions
You can define a regular expression that the parameter should match:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
```Python hl_lines="11"
- {!> ../../../docs_src/query_params_str_validations/tutorial004.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial004_an_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.9+"
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/query_params_str_validations/tutorial004_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/query_params_str_validations/tutorial004_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="9"
{!> ../../../docs_src/query_params_str_validations/tutorial004_py310.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/query_params_str_validations/tutorial004.py!}
+ ```
+
This specific regular expression checks that the received parameter value:
* `^`: starts with the following characters, doesn't have characters before.
@@ -156,16 +320,33 @@ But whenever you need them and go and learn them, know that you can already use
## Default values
-The same way that you can pass `None` as the value for the `default` parameter, you can pass other values.
+You can, of course, use default values other than `None`.
Let's say that you want to declare the `q` query parameter to have a `min_length` of `3`, and to have a default value of `"fixedquery"`:
-```Python hl_lines="7"
-{!../../../docs_src/query_params_str_validations/tutorial005.py!}
-```
+=== "Python 3.9+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial005_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params_str_validations/tutorial005_an.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial005.py!}
+ ```
!!! note
- Having a default value also makes the parameter optional.
+ Having a default value of any type, including `None`, makes the parameter optional (not required).
## Make it required
@@ -183,23 +364,70 @@ q: Union[str, None] = None
But we are now declaring it with `Query`, for example like:
-```Python
-q: Union[str, None] = Query(default=None, min_length=3)
-```
+=== "Annotated"
+
+ ```Python
+ q: Annotated[Union[str, None], Query(min_length=3)] = None
+ ```
+
+=== "non-Annotated"
+
+ ```Python
+ q: Union[str, None] = Query(default=None, min_length=3)
+ ```
So, when you need to declare a value as required while using `Query`, you can simply not declare a default value:
-```Python hl_lines="7"
-{!../../../docs_src/query_params_str_validations/tutorial006.py!}
-```
+=== "Python 3.9+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006_an.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006.py!}
+ ```
+
+ !!! tip
+ Notice that, even though in this case the `Query()` is used as the function parameter default value, we don't pass the `default=None` to `Query()`.
+
+ Still, probably better to use the `Annotated` version. 😉
### Required with Ellipsis (`...`)
-There's an alternative way to explicitly declare that a value is required. You can set the `default` parameter to the literal value `...`:
+There's an alternative way to explicitly declare that a value is required. You can set the default to the literal value `...`:
-```Python hl_lines="7"
-{!../../../docs_src/query_params_str_validations/tutorial006b.py!}
-```
+=== "Python 3.9+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006b_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006b_an.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006b.py!}
+ ```
!!! info
If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis".
@@ -212,20 +440,44 @@ This will let **FastAPI** know that this parameter is required.
You can declare that a parameter can accept `None`, but that it's still required. This would force clients to send a value, even if the value is `None`.
-To do that, you can declare that `None` is a valid type but still use `default=...`:
+To do that, you can declare that `None` is a valid type but still use `...` as the default:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
```Python hl_lines="9"
- {!> ../../../docs_src/query_params_str_validations/tutorial006c.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial006c_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006c_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006c_an.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="7"
{!> ../../../docs_src/query_params_str_validations/tutorial006c_py310.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006c.py!}
+ ```
+
!!! tip
Pydantic, which is what powers all the data validation and serialization in FastAPI, has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about Required Optional fields.
@@ -233,12 +485,29 @@ To do that, you can declare that `None` is a valid type but still use `default=.
If you feel uncomfortable using `...`, you can also import and use `Required` from Pydantic:
-```Python hl_lines="2 8"
-{!../../../docs_src/query_params_str_validations/tutorial006d.py!}
-```
+=== "Python 3.9+"
+
+ ```Python hl_lines="4 10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006d_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="2 9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006d_an.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="2 8"
+ {!> ../../../docs_src/query_params_str_validations/tutorial006d.py!}
+ ```
!!! tip
- Remember that in most of the cases, when something is required, you can simply omit the `default` parameter, so you normally don't have to use `...` nor `Required`.
+ Remember that in most of the cases, when something is required, you can simply omit the default, so you normally don't have to use `...` nor `Required`.
## Query parameter list / multiple values
@@ -246,24 +515,51 @@ When you define a query parameter explicitly with `Query` you can also declare i
For example, to declare a query parameter `q` that can appear multiple times in the URL, you can write:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
```Python hl_lines="9"
- {!> ../../../docs_src/query_params_str_validations/tutorial011.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_an_py310.py!}
```
-=== "Python 3.9 and above"
+=== "Python 3.9+"
```Python hl_lines="9"
- {!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_an_py39.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="7"
{!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!}
```
+=== "Python 3.9+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011.py!}
+ ```
+
Then, with a URL like:
```
@@ -294,18 +590,36 @@ The interactive API docs will update accordingly, to allow multiple values:
And you can also define a default `list` of values if none are provided:
-=== "Python 3.6 and above"
+=== "Python 3.9+"
```Python hl_lines="9"
- {!> ../../../docs_src/query_params_str_validations/tutorial012.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial012_an_py39.py!}
```
-=== "Python 3.9 and above"
+=== "Python 3.6+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial012_an.py!}
+ ```
+
+=== "Python 3.9+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="7"
{!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial012.py!}
+ ```
+
If you go to:
```
@@ -327,9 +641,26 @@ the default of `q` will be: `["foo", "bar"]` and your response will be:
You can also use `list` directly instead of `List[str]` (or `list[str]` in Python 3.9+):
-```Python hl_lines="7"
-{!../../../docs_src/query_params_str_validations/tutorial013.py!}
-```
+=== "Python 3.9+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial013_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params_str_validations/tutorial013_an.py!}
+ ```
+
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial013.py!}
+ ```
!!! note
Have in mind that in this case, FastAPI won't check the contents of the list.
@@ -349,32 +680,80 @@ That information will be included in the generated OpenAPI and used by the docum
You can add a `title`:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
```Python hl_lines="10"
- {!> ../../../docs_src/query_params_str_validations/tutorial007.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial007_an_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.9+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial007_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/query_params_str_validations/tutorial007_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="8"
{!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial007.py!}
+ ```
+
And a `description`:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="13"
- {!> ../../../docs_src/query_params_str_validations/tutorial008.py!}
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008_an_py39.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="12"
{!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008.py!}
+ ```
+
## Alias parameters
Imagine that you want the parameter to be `item-query`.
@@ -393,18 +772,42 @@ But you still need it to be exactly `item-query`...
Then you can declare an `alias`, and that alias is what will be used to find the parameter value:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
```Python hl_lines="9"
- {!> ../../../docs_src/query_params_str_validations/tutorial009.py!}
+ {!> ../../../docs_src/query_params_str_validations/tutorial009_an_py310.py!}
+ ```
+
+=== "Python 3.9+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial009_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial009_an.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="7"
{!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial009.py!}
+ ```
+
## Deprecating parameters
Now let's say you don't like this parameter anymore.
@@ -413,18 +816,42 @@ You have to leave it there a while because there are clients using it, but you w
Then pass the parameter `deprecated=True` to `Query`:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="18"
- {!> ../../../docs_src/query_params_str_validations/tutorial010.py!}
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010_an_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.9+"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010_an_py39.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010_an.py!}
+ ```
+
+=== "Python 3.10+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
```Python hl_lines="17"
{!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!}
```
+=== "Python 3.6+ non-Annotated"
+
+ !!! tip
+ Prefer to use the `Annotated` version if possible.
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010.py!}
+ ```
+
The docs will show it like this:
+ FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def
...uvicorn main:app --reload
...ujson
- for faster JSON "parsing".
+* email_validator
- for email validation.
+
+Used by Starlette:
+
+* httpx
- Required if you want to use the `TestClient`.
+* jinja2
- Required if you want to use the default template configuration.
+* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
+* itsdangerous
- Required for `SessionMiddleware` support.
+* pyyaml
- Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
+* ujson
- Required if you want to use `UJSONResponse`.
+
+Used by FastAPI / Starlette:
+
+* uvicorn
- for the server that loads and serves your application.
+* orjson
- Required if you want to use `ORJSONResponse`.
+
+You can install all of these with `pip install "fastapi[all]"`.
+
+## License
+
+This project is licensed under the terms of the MIT license.
diff --git a/docs/hy/mkdocs.yml b/docs/hy/mkdocs.yml
new file mode 100644
index 000000000..bc64e78f2
--- /dev/null
+++ b/docs/hy/mkdocs.yml
@@ -0,0 +1,148 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/hy/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - media: '(prefers-color-scheme: light)'
+ scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - media: '(prefers-color-scheme: dark)'
+ scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: hy
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - he: /he/
+ - hy: /hy/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - sv: /sv/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+- attr_list
+- md_in_html
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /he/
+ name: he
+ - link: /hy/
+ name: hy
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /sv/
+ name: sv - svenska
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/hy/overrides/.gitignore b/docs/hy/overrides/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml
index abb31252f..7b7875ef7 100644
--- a/docs/id/mkdocs.yml
+++ b/docs/id/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -80,7 +82,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -111,6 +113,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -131,6 +135,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml
index 532b5bc52..9393c3663 100644
--- a/docs/it/mkdocs.yml
+++ b/docs/it/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -80,7 +82,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -111,6 +113,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -131,6 +135,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/ja/docs/tutorial/testing.md b/docs/ja/docs/tutorial/testing.md
index 56f5cabac..037e9628f 100644
--- a/docs/ja/docs/tutorial/testing.md
+++ b/docs/ja/docs/tutorial/testing.md
@@ -74,16 +74,16 @@
これらの *path operation* には `X-Token` ヘッダーが必要です。
-=== "Python 3.6 and above"
+=== "Python 3.10+"
```Python
- {!> ../../../docs_src/app_testing/app_b/main.py!}
+ {!> ../../../docs_src/app_testing/app_b_py310/main.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
```Python
- {!> ../../../docs_src/app_testing/app_b_py310/main.py!}
+ {!> ../../../docs_src/app_testing/app_b/main.py!}
```
### 拡張版テストファイル
diff --git a/docs/ja/mkdocs.yml b/docs/ja/mkdocs.yml
index 5bbcce605..3703398af 100644
--- a/docs/ja/mkdocs.yml
+++ b/docs/ja/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -124,7 +126,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -155,6 +157,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -175,6 +179,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/ko/docs/tutorial/cors.md b/docs/ko/docs/tutorial/cors.md
new file mode 100644
index 000000000..39e9ea83f
--- /dev/null
+++ b/docs/ko/docs/tutorial/cors.md
@@ -0,0 +1,84 @@
+# 교차 출처 리소스 공유
+
+CORS 또는 "교차-출처 리소스 공유"란, 브라우저에서 동작하는 프론트엔드가 자바스크립트로 코드로 백엔드와 통신하고, 백엔드는 해당 프론트엔드와 다른 "출처"에 존재하는 상황을 의미합니다.
+
+## 출처
+
+출처란 프로토콜(`http` , `https`), 도메인(`myapp.com`, `localhost`, `localhost.tiangolo.com` ), 그리고 포트(`80`, `443`, `8080` )의 조합을 의미합니다.
+
+따라서, 아래는 모두 상이한 출처입니다:
+
+* `http://localhost`
+* `https://localhost`
+* `http://localhost:8080`
+
+모두 `localhost` 에 있지만, 서로 다른 프로토콜과 포트를 사용하고 있으므로 다른 "출처"입니다.
+
+## 단계
+
+브라우저 내 `http://localhost:8080`에서 동작하는 프론트엔드가 있고, 자바스크립트는 `http://localhost`를 통해 백엔드와 통신한다고 가정해봅시다(포트를 명시하지 않는 경우, 브라우저는 `80` 을 기본 포트로 간주합니다).
+
+그러면 브라우저는 백엔드에 HTTP `OPTIONS` 요청을 보내고, 백엔드에서 이 다른 출처(`http://localhost:8080`)와의 통신을 허가하는 적절한 헤더를 보내면, 브라우저는 프론트엔드의 자바스크립트가 백엔드에 요청을 보낼 수 있도록 합니다.
+
+이를 위해, 백엔드는 "허용된 출처(allowed origins)" 목록을 가지고 있어야만 합니다.
+
+이 경우, 프론트엔드가 제대로 동작하기 위해 `http://localhost:8080`을 목록에 포함해야 합니다.
+
+## 와일드카드
+
+모든 출처를 허용하기 위해 목록을 `"*"` ("와일드카드")로 선언하는 것도 가능합니다.
+
+하지만 이것은 특정한 유형의 통신만을 허용하며, 쿠키 및 액세스 토큰과 사용되는 인증 헤더(Authoriztion header) 등이 포함된 경우와 같이 자격 증명(credentials)이 포함된 통신은 허용되지 않습니다.
+
+따라서 모든 작업을 의도한대로 실행하기 위해, 허용되는 출처를 명시적으로 지정하는 것이 좋습니다.
+
+## `CORSMiddleware` 사용
+
+`CORSMiddleware` 을 사용하여 **FastAPI** 응용 프로그램의 교차 출처 리소스 공유 환경을 설정할 수 있습니다.
+
+* `CORSMiddleware` 임포트.
+* 허용되는 출처(문자열 형식)의 리스트 생성.
+* FastAPI 응용 프로그램에 "미들웨어(middleware)"로 추가.
+
+백엔드에서 다음의 사항을 허용할지에 대해 설정할 수도 있습니다:
+
+* 자격증명 (인증 헤더, 쿠키 등).
+* 특정한 HTTP 메소드(`POST`, `PUT`) 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 메소드.
+* 특정한 HTTP 헤더 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 헤더.
+
+```Python hl_lines="2 6-11 13-19"
+{!../../../docs_src/cors/tutorial001.py!}
+```
+
+`CORSMiddleware` 에서 사용하는 기본 매개변수는 제한적이므로, 브라우저가 교차-도메인 상황에서 특정한 출처, 메소드, 헤더 등을 사용할 수 있도록 하려면 이들을 명시적으로 허용해야 합니다.
+
+다음의 인자들이 지원됩니다:
+
+* `allow_origins` - 교차-출처 요청을 보낼 수 있는 출처의 리스트입니다. 예) `['https://example.org', 'https://www.example.org']`. 모든 출처를 허용하기 위해 `['*']` 를 사용할 수 있습니다.
+* `allow_origin_regex` - 교차-출처 요청을 보낼 수 있는 출처를 정규표현식 문자열로 나타냅니다. `'https://.*\.example\.org'`.
+* `allow_methods` - 교차-출처 요청을 허용하는 HTTP 메소드의 리스트입니다. 기본값은 `['GET']` 입니다. `['*']` 을 사용하여 모든 표준 메소드들을 허용할 수 있습니다.
+* `allow_headers` - 교차-출처를 지원하는 HTTP 요청 헤더의 리스트입니다. 기본값은 `[]` 입니다. 모든 헤더들을 허용하기 위해 `['*']` 를 사용할 수 있습니다. `Accept`, `Accept-Language`, `Content-Language` 그리고 `Content-Type` 헤더는 CORS 요청시 언제나 허용됩니다.
+* `allow_credentials` - 교차-출처 요청시 쿠키 지원 여부를 설정합니다. 기본값은 `False` 입니다. 또한 해당 항목을 허용할 경우 `allow_origins` 는 `['*']` 로 설정할 수 없으며, 출처를 반드시 특정해야 합니다.
+* `expose_headers` - 브라우저에 접근할 수 있어야 하는 모든 응답 헤더를 가리킵니다. 기본값은 `[]` 입니다.
+* `max_age` - 브라우저가 CORS 응답을 캐시에 저장하는 최대 시간을 초 단위로 설정합니다. 기본값은 `600` 입니다.
+
+미들웨어는 두가지 특정한 종류의 HTTP 요청에 응답합니다...
+
+### CORS 사전 요청
+
+`Origin` 및 `Access-Control-Request-Method` 헤더와 함께 전송하는 모든 `OPTIONS` 요청입니다.
+
+이 경우 미들웨어는 들어오는 요청을 가로채 적절한 CORS 헤더와, 정보 제공을 위한 `200` 또는 `400` 응답으로 응답합니다.
+
+### 단순한 요청
+
+`Origin` 헤더를 가진 모든 요청. 이 경우 미들웨어는 요청을 정상적으로 전달하지만, 적절한 CORS 헤더를 응답에 포함시킵니다.
+
+## 더 많은 정보
+
+CORS에 대한 더 많은 정보를 알고싶다면, Mozilla CORS 문서를 참고하기 바랍니다.
+
+!!! note "기술적 세부 사항"
+ `from starlette.middleware.cors import CORSMiddleware` 역시 사용할 수 있습니다.
+
+ **FastAPI**는 개발자인 당신의 편의를 위해 `fastapi.middleware` 에서 몇가지의 미들웨어를 제공합니다. 하지만 대부분의 미들웨어가 Stralette으로부터 직접 제공됩니다.
diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml
index 50931e134..29b684371 100644
--- a/docs/ko/mkdocs.yml
+++ b/docs/ko/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -69,6 +71,7 @@ nav:
- tutorial/request-files.md
- tutorial/request-forms-and-files.md
- tutorial/encoder.md
+ - tutorial/cors.md
markdown_extensions:
- toc:
permalink: true
@@ -91,7 +94,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -122,6 +125,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -142,6 +147,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/nl/mkdocs.yml b/docs/nl/mkdocs.yml
index 6d46939f9..d9b1bc1b8 100644
--- a/docs/nl/mkdocs.yml
+++ b/docs/nl/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -80,7 +82,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -111,6 +113,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -131,6 +135,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml
index 1cd129420..8d0d20239 100644
--- a/docs/pl/mkdocs.yml
+++ b/docs/pl/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -83,7 +85,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -114,6 +116,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -134,6 +138,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/pt/docs/tutorial/body-multiple-params.md b/docs/pt/docs/tutorial/body-multiple-params.md
index ac67aa47f..22f5856a6 100644
--- a/docs/pt/docs/tutorial/body-multiple-params.md
+++ b/docs/pt/docs/tutorial/body-multiple-params.md
@@ -8,16 +8,16 @@ Primeiro, é claro, você pode misturar `Path`, `Query` e declarações de parâ
E você também pode declarar parâmetros de corpo como opcionais, definindo o valor padrão com `None`:
-=== "Python 3.6 e superiores"
+=== "Python 3.10+"
- ```Python hl_lines="19-21"
- {!> ../../../docs_src/body_multiple_params/tutorial001.py!}
+ ```Python hl_lines="17-19"
+ {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!}
```
-=== "Python 3.10 e superiores"
+=== "Python 3.6+"
- ```Python hl_lines="17-19"
- {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!}
+ ```Python hl_lines="19-21"
+ {!> ../../../docs_src/body_multiple_params/tutorial001.py!}
```
!!! nota
@@ -38,16 +38,16 @@ No exemplo anterior, as *operações de rota* esperariam um JSON no corpo conten
Mas você pode também declarar múltiplos parâmetros de corpo, por exemplo, `item` e `user`:
-=== "Python 3.6 e superiores"
+=== "Python 3.10+"
- ```Python hl_lines="22"
- {!> ../../../docs_src/body_multiple_params/tutorial002.py!}
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!}
```
-=== "Python 3.10 e superiores"
+=== "Python 3.6+"
- ```Python hl_lines="20"
- {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!}
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/body_multiple_params/tutorial002.py!}
```
Neste caso, o **FastAPI** perceberá que existe mais de um parâmetro de corpo na função (dois parâmetros que são modelos Pydantic).
@@ -87,13 +87,13 @@ Se você declará-lo como é, porque é um valor singular, o **FastAPI** assumir
Mas você pode instruir o **FastAPI** para tratá-lo como outra chave do corpo usando `Body`:
-=== "Python 3.6 e superiores"
+=== "Python 3.6+"
```Python hl_lines="22"
{!> ../../../docs_src/body_multiple_params/tutorial003.py!}
```
-=== "Python 3.10 e superiores"
+=== "Python 3.10+"
```Python hl_lines="20"
{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!}
@@ -137,16 +137,16 @@ q: str | None = None
Por exemplo:
-=== "Python 3.6 e superiores"
+=== "Python 3.10+"
- ```Python hl_lines="27"
- {!> ../../../docs_src/body_multiple_params/tutorial004.py!}
+ ```Python hl_lines="26"
+ {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!}
```
-=== "Python 3.10 e superiores"
+=== "Python 3.6+"
- ```Python hl_lines="26"
- {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!}
+ ```Python hl_lines="27"
+ {!> ../../../docs_src/body_multiple_params/tutorial004.py!}
```
!!! info "Informação"
@@ -166,16 +166,16 @@ item: Item = Body(embed=True)
como em:
-=== "Python 3.6 e superiores"
+=== "Python 3.10+"
- ```Python hl_lines="17"
- {!> ../../../docs_src/body_multiple_params/tutorial005.py!}
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!}
```
-=== "Python 3.10 e superiores"
+=== "Python 3.6+"
- ```Python hl_lines="15"
- {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!}
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/body_multiple_params/tutorial005.py!}
```
Neste caso o **FastAPI** esperará um corpo como:
diff --git a/docs/pt/docs/tutorial/encoder.md b/docs/pt/docs/tutorial/encoder.md
new file mode 100644
index 000000000..bb4483fdc
--- /dev/null
+++ b/docs/pt/docs/tutorial/encoder.md
@@ -0,0 +1,42 @@
+# Codificador Compatível com JSON
+
+Existem alguns casos em que você pode precisar converter um tipo de dados (como um modelo Pydantic) para algo compatível com JSON (como um `dict`, `list`, etc).
+
+Por exemplo, se você precisar armazená-lo em um banco de dados.
+
+Para isso, **FastAPI** fornece uma função `jsonable_encoder()`.
+
+## Usando a função `jsonable_encoder`
+
+Vamos imaginar que você tenha um banco de dados `fake_db` que recebe apenas dados compatíveis com JSON.
+
+Por exemplo, ele não recebe objetos `datetime`, pois estes objetos não são compatíveis com JSON.
+
+Então, um objeto `datetime` teria que ser convertido em um `str` contendo os dados no formato ISO.
+
+Da mesma forma, este banco de dados não receberia um modelo Pydantic (um objeto com atributos), apenas um `dict`.
+
+Você pode usar a função `jsonable_encoder` para resolver isso.
+
+A função recebe um objeto, como um modelo Pydantic e retorna uma versão compatível com JSON:
+
+=== "Python 3.10+"
+
+ ```Python hl_lines="4 21"
+ {!> ../../../docs_src/encoder/tutorial001_py310.py!}
+ ```
+
+=== "Python 3.6+"
+
+ ```Python hl_lines="5 22"
+ {!> ../../../docs_src/encoder/tutorial001.py!}
+ ```
+
+Neste exemplo, ele converteria o modelo Pydantic em um `dict`, e o `datetime` em um `str`.
+
+O resultado de chamar a função é algo que pode ser codificado com o padrão do Python `json.dumps()`.
+
+A função não retorna um grande `str` contendo os dados no formato JSON (como uma string). Mas sim, retorna uma estrutura de dados padrão do Python (por exemplo, um `dict`) com valores e subvalores compatíveis com JSON.
+
+!!! nota
+ `jsonable_encoder` é realmente usado pelo **FastAPI** internamente para converter dados. Mas também é útil em muitos outros cenários.
diff --git a/docs/pt/docs/tutorial/header-params.md b/docs/pt/docs/tutorial/header-params.md
index 94ee784cd..bc8843327 100644
--- a/docs/pt/docs/tutorial/header-params.md
+++ b/docs/pt/docs/tutorial/header-params.md
@@ -6,16 +6,16 @@ Você pode definir parâmetros de Cabeçalho da mesma maneira que define paramê
Primeiro importe `Header`:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="3"
- {!> ../../../docs_src/header_params/tutorial001.py!}
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/header_params/tutorial001_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="1"
- {!> ../../../docs_src/header_params/tutorial001_py310.py!}
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/header_params/tutorial001.py!}
```
## Declare parâmetros de `Header`
@@ -24,16 +24,16 @@ Então declare os paramêtros de cabeçalho usando a mesma estrutura que em `Pat
O primeiro valor é o valor padrão, você pode passar todas as validações adicionais ou parâmetros de anotação:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/header_params/tutorial001.py!}
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/header_params/tutorial001_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="7"
- {!> ../../../docs_src/header_params/tutorial001_py310.py!}
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial001.py!}
```
!!! note "Detalhes Técnicos"
@@ -60,16 +60,16 @@ Portanto, você pode usar `user_agent` como faria normalmente no código Python,
Se por algum motivo você precisar desabilitar a conversão automática de sublinhados para hífens, defina o parâmetro `convert_underscores` de `Header` para `False`:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="10"
- {!> ../../../docs_src/header_params/tutorial002.py!}
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/header_params/tutorial002_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="8"
- {!> ../../../docs_src/header_params/tutorial002_py310.py!}
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/header_params/tutorial002.py!}
```
!!! warning "Aviso"
@@ -85,22 +85,22 @@ Você receberá todos os valores do cabeçalho duplicado como uma `list` Python.
Por exemplo, para declarar um cabeçalho de `X-Token` que pode aparecer mais de uma vez, você pode escrever:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/header_params/tutorial003.py!}
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/header_params/tutorial003_py310.py!}
```
-=== "Python 3.9 and above"
+=== "Python 3.9+"
```Python hl_lines="9"
{!> ../../../docs_src/header_params/tutorial003_py39.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="7"
- {!> ../../../docs_src/header_params/tutorial003_py310.py!}
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial003.py!}
```
Se você se comunicar com essa *operação de caminho* enviando dois cabeçalhos HTTP como:
diff --git a/docs/pt/docs/tutorial/path-params-numeric-validations.md b/docs/pt/docs/tutorial/path-params-numeric-validations.md
index f478fd190..ec9b74b30 100644
--- a/docs/pt/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/pt/docs/tutorial/path-params-numeric-validations.md
@@ -6,16 +6,16 @@ Do mesmo modo que você pode declarar mais validações e metadados para parâme
Primeiro, importe `Path` de `fastapi`:
-=== "Python 3.6 e superiores"
+=== "Python 3.10+"
- ```Python hl_lines="3"
- {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
```
-=== "Python 3.10 e superiores"
+=== "Python 3.6+"
- ```Python hl_lines="1"
- {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
```
## Declare metadados
@@ -24,16 +24,16 @@ Você pode declarar todos os parâmetros da mesma maneira que na `Query`.
Por exemplo para declarar um valor de metadado `title` para o parâmetro de rota `item_id` você pode digitar:
-=== "Python 3.6 e superiores"
+=== "Python 3.10+"
- ```Python hl_lines="10"
- {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
```
-=== "Python 3.10 e superiores"
+=== "Python 3.6+"
- ```Python hl_lines="8"
- {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
```
!!! note "Nota"
diff --git a/docs/pt/docs/tutorial/query-params.md b/docs/pt/docs/tutorial/query-params.md
index 189724396..3ada4fd21 100644
--- a/docs/pt/docs/tutorial/query-params.md
+++ b/docs/pt/docs/tutorial/query-params.md
@@ -63,16 +63,16 @@ Os valores dos parâmetros na sua função serão:
Da mesma forma, você pode declarar parâmetros de consulta opcionais, definindo o valor padrão para `None`:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/query_params/tutorial002.py!}
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial002_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="7"
- {!> ../../../docs_src/query_params/tutorial002_py310.py!}
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial002.py!}
```
Nesse caso, o parâmetro da função `q` será opcional, e `None` será o padrão.
@@ -85,16 +85,16 @@ Nesse caso, o parâmetro da função `q` será opcional, e `None` será o padrã
Você também pode declarar tipos `bool`, e eles serão convertidos:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/query_params/tutorial003.py!}
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial003_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="7"
- {!> ../../../docs_src/query_params/tutorial003_py310.py!}
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial003.py!}
```
Nesse caso, se você for para:
@@ -137,16 +137,16 @@ E você não precisa declarar eles em nenhuma ordem específica.
Eles serão detectados pelo nome:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="8 10"
- {!> ../../../docs_src/query_params/tutorial004.py!}
+ ```Python hl_lines="6 8"
+ {!> ../../../docs_src/query_params/tutorial004_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="6 8"
- {!> ../../../docs_src/query_params/tutorial004_py310.py!}
+ ```Python hl_lines="8 10"
+ {!> ../../../docs_src/query_params/tutorial004.py!}
```
## Parâmetros de consulta obrigatórios
@@ -203,17 +203,18 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
E claro, você pode definir alguns parâmetros como obrigatórios, alguns possuindo um valor padrão, e outros sendo totalmente opcionais:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="10"
- {!> ../../../docs_src/query_params/tutorial006.py!}
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params/tutorial006_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="8"
- {!> ../../../docs_src/query_params/tutorial006_py310.py!}
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params/tutorial006.py!}
```
+
Nesse caso, existem 3 parâmetros de consulta:
* `needy`, um `str` obrigatório.
diff --git a/docs/pt/docs/tutorial/static-files.md b/docs/pt/docs/tutorial/static-files.md
new file mode 100644
index 000000000..009158fc6
--- /dev/null
+++ b/docs/pt/docs/tutorial/static-files.md
@@ -0,0 +1,39 @@
+# Arquivos Estáticos
+
+Você pode servir arquivos estáticos automaticamente de um diretório usando `StaticFiles`.
+
+## Use `StaticFiles`
+
+* Importe `StaticFiles`.
+* "Monte" uma instância de `StaticFiles()` em um caminho específico.
+
+```Python hl_lines="2 6"
+{!../../../docs_src/static_files/tutorial001.py!}
+```
+
+!!! note "Detalhes técnicos"
+ Você também pode usar `from starlette.staticfiles import StaticFiles`.
+
+ O **FastAPI** fornece o mesmo que `starlette.staticfiles` como `fastapi.staticfiles` apenas como uma conveniência para você, o desenvolvedor. Mas na verdade vem diretamente da Starlette.
+
+### O que é "Montagem"
+
+"Montagem" significa adicionar um aplicativo completamente "independente" em uma rota específica, que então cuida de todas as subrotas.
+
+Isso é diferente de usar um `APIRouter`, pois um aplicativo montado é completamente independente. A OpenAPI e a documentação do seu aplicativo principal não incluirão nada do aplicativo montado, etc.
+
+Você pode ler mais sobre isso no **Guia Avançado do Usuário**.
+
+## Detalhes
+
+O primeiro `"/static"` refere-se à subrota em que este "subaplicativo" será "montado". Portanto, qualquer caminho que comece com `"/static"` será tratado por ele.
+
+O `directory="static"` refere-se ao nome do diretório que contém seus arquivos estáticos.
+
+O `name="static"` dá a ela um nome que pode ser usado internamente pelo FastAPI.
+
+Todos esses parâmetros podem ser diferentes de "`static`", ajuste-os de acordo com as necessidades e detalhes específicos de sua própria aplicação.
+
+## Mais informações
+
+Para mais detalhes e opções, verifique Starlette's docs about Static Files.
diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml
index 0858de062..2a8302715 100644
--- a/docs/pt/mkdocs.yml
+++ b/docs/pt/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -77,9 +79,11 @@ nav:
- tutorial/request-forms.md
- tutorial/request-forms-and-files.md
- tutorial/handling-errors.md
+ - tutorial/encoder.md
- Segurança:
- tutorial/security/index.md
- tutorial/background-tasks.md
+ - tutorial/static-files.md
- Guia de Usuário Avançado:
- advanced/index.md
- Implantação:
@@ -115,7 +119,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -146,6 +150,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -166,6 +172,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/ru/docs/contributing.md b/docs/ru/docs/contributing.md
new file mode 100644
index 000000000..cb460beb0
--- /dev/null
+++ b/docs/ru/docs/contributing.md
@@ -0,0 +1,469 @@
+# Участие в разработке фреймворка
+
+Возможно, для начала Вам стоит ознакомиться с основными способами [помочь FastAPI или получить помощь](help-fastapi.md){.internal-link target=_blank}.
+
+## Разработка
+
+Если Вы уже склонировали репозиторий и знаете, что Вам нужно более глубокое погружение в код фреймворка, то здесь представлены некоторые инструкции по настройке виртуального окружения.
+
+### Виртуальное окружение с помощью `venv`
+
+Находясь в нужной директории, Вы можете создать виртуальное окружение при помощи Python модуля `venv`.
+
++ +**FastAPI** не существовал бы, если б не было более ранних работ других людей. + +Они создали большое количество инструментов, которые и вдохновили меня на создание **FastAPI**. + +Я всячески избегал создания нового фреймворка в течение нескольких лет. Сначала я пытался собрать все нужные возможности, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов. + +Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти возможности сразу. Взять самые лучшие идеи из предыдущих инструментов и, используя введённые в Python подсказки типов (которых не было до версии 3.6), объединить их. + ++ +## Исследования + +Благодаря опыту использования существующих альтернатив, мы с коллегами изучили их основные идеи и скомбинировали собранные знания наилучшим образом. + +Например, стало ясно, что необходимо брать за основу стандартные подсказки типов Python, а самым лучшим подходом является использование уже существующих стандартов. + +Итак, прежде чем приступить к написанию **FastAPI**, я потратил несколько месяцев на изучение OpenAPI, JSON Schema, OAuth2, и т.п. для понимания их взаимосвязей, совпадений и различий. + +## Дизайн + +Затем я потратил некоторое время на придумывание "API" разработчика, который я хотел иметь как пользователь (как разработчик, использующий FastAPI). + +Я проверил несколько идей на самых популярных редакторах кода среди Python-разработчиков: PyCharm, VS Code, Jedi. + +Данные по редакторам я взял из опроса Python-разработчиков, который охватываает около 80% пользователей. + +Это означает, что **FastAPI** был специально проверен на редакторах, используемых 80% Python-разработчиками. И поскольку большинство других редакторов, как правило, работают аналогичным образом, все его преимущества должны работать практически для всех редакторов. + +Таким образом, я смог найти наилучшие способы сократить дублирование кода, обеспечить повсеместное автодополнение, проверку типов и ошибок и т.д. + +И все это, чтобы все пользователи могли получать наилучший опыт разработки. + +## Зависимости + +Протестировав несколько вариантов, я решил, что в качестве основы буду использовать **Pydantic** и его преимущества. + +По моим предложениям был изменён код этого фреймворка, чтобы сделать его полностью совместимым с JSON Schema, поддержать различные способы определения ограничений и улучшить помощь редакторов (проверки типов, автозаполнение). + +В то же время, я принимал участие в разработке **Starlette**, ещё один из основных компонентов FastAPI. + +## Разработка + +К тому времени, когда я начал создавать **FastAPI**, большинство необходимых деталей уже существовало, дизайн был определён, зависимости и прочие инструменты были готовы, а знания о стандартах и спецификациях были четкими и свежими. + +## Будущее + +Сейчас уже ясно, что **FastAPI** со своими идеями стал полезен многим людям. + +При сравнении с альтернативами, выбор падает на него, поскольку он лучше подходит для множества вариантов использования. + +Многие разработчики и команды уже используют **FastAPI** в своих проектах (включая меня и мою команду). + +Но, тем не менее, грядёт добавление ещё многих улучшений и возможностей. + +У **FastAPI** великое будущее. + +И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценнен. diff --git a/docs/ru/docs/tutorial/background-tasks.md b/docs/ru/docs/tutorial/background-tasks.md index e608f6c8f..81efda786 100644 --- a/docs/ru/docs/tutorial/background-tasks.md +++ b/docs/ru/docs/tutorial/background-tasks.md @@ -57,16 +57,16 @@ **FastAPI** знает, что нужно сделать в каждом случае и как переиспользовать тот же объект `BackgroundTasks`, так чтобы все фоновые задачи собрались и запустились вместе в фоне: -=== "Python 3.6 и выше" +=== "Python 3.10+" - ```Python hl_lines="13 15 22 25" - {!> ../../../docs_src/background_tasks/tutorial002.py!} + ```Python hl_lines="11 13 20 23" + {!> ../../../docs_src/background_tasks/tutorial002_py310.py!} ``` -=== "Python 3.10 и выше" +=== "Python 3.6+" - ```Python hl_lines="11 13 20 23" - {!> ../../../docs_src/background_tasks/tutorial002_py310.py!} + ```Python hl_lines="13 15 22 25" + {!> ../../../docs_src/background_tasks/tutorial002.py!} ``` В этом примере сообщения будут записаны в `log.txt` *после* того, как ответ сервера был отправлен. diff --git a/docs/ru/docs/tutorial/body-fields.md b/docs/ru/docs/tutorial/body-fields.md new file mode 100644 index 000000000..674b8bde4 --- /dev/null +++ b/docs/ru/docs/tutorial/body-fields.md @@ -0,0 +1,69 @@ +# Body - Поля + +Таким же способом, как вы объявляете дополнительную валидацию и метаданные в параметрах *функции обработки пути* с помощью функций `Query`, `Path` и `Body`, вы можете объявлять валидацию и метаданные внутри Pydantic моделей, используя функцию `Field` из Pydantic. + +## Импорт `Field` + +Сначала вы должны импортировать его: + +=== "Python 3.10+" + + ```Python hl_lines="2" + {!> ../../../docs_src/body_fields/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4" + {!> ../../../docs_src/body_fields/tutorial001.py!} + ``` + +!!! warning "Внимание" + Обратите внимание, что функция `Field` импортируется непосредственно из `pydantic`, а не из `fastapi`, как все остальные функции (`Query`, `Path`, `Body` и т.д.). + +## Объявление атрибутов модели + +Вы можете использовать функцию `Field` с атрибутами модели: + +=== "Python 3.10+" + + ```Python hl_lines="9-12" + {!> ../../../docs_src/body_fields/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11-14" + {!> ../../../docs_src/body_fields/tutorial001.py!} + ``` + +Функция `Field` работает так же, как `Query`, `Path` и `Body`, у ее такие же параметры и т.д. + +!!! note "Технические детали" + На самом деле, `Query`, `Path` и другие функции, которые вы увидите в дальнейшем, создают объекты подклассов общего класса `Param`, который сам по себе является подклассом `FieldInfo` из Pydantic. + + И `Field` (из Pydantic), и `Body`, оба возвращают объекты подкласса `FieldInfo`. + + У класса `Body` есть и другие подклассы, с которыми вы ознакомитесь позже. + + Помните, что когда вы импортируете `Query`, `Path` и другое из `fastapi`, это фактически функции, которые возвращают специальные классы. + +!!! tip "Подсказка" + Обратите внимание, что каждый атрибут модели с типом, значением по умолчанию и `Field` имеет ту же структуру, что и параметр *функции обработки пути* с `Field` вместо `Path`, `Query` и `Body`. + +## Добавление дополнительной информации + +Вы можете объявлять дополнительную информацию в `Field`, `Query`, `Body` и т.п. Она будет включена в сгенерированную JSON схему. + +Вы узнаете больше о добавлении дополнительной информации позже в документации, когда будете изучать, как задавать примеры принимаемых данных. + + +!!! warning "Внимание" + Дополнительные ключи, переданные в функцию `Field`, также будут присутствовать в сгенерированной OpenAPI схеме вашего приложения. + Поскольку эти ключи не являются обязательной частью спецификации OpenAPI, некоторые инструменты OpenAPI, например, [валидатор OpenAPI](https://validator.swagger.io/), могут не работать с вашей сгенерированной схемой. + +## Резюме + +Вы можете использовать функцию `Field` из Pydantic, чтобы задавать дополнительную валидацию и метаданные для атрибутов модели. + +Вы также можете использовать дополнительные ключевые аргументы, чтобы добавить метаданные JSON схемы. diff --git a/docs/ru/docs/tutorial/cookie-params.md b/docs/ru/docs/tutorial/cookie-params.md new file mode 100644 index 000000000..a6f2caa26 --- /dev/null +++ b/docs/ru/docs/tutorial/cookie-params.md @@ -0,0 +1,49 @@ +# Параметры Cookie + +Вы можете задать параметры Cookie таким же способом, как `Query` и `Path` параметры. + +## Импорт `Cookie` + +Сначала импортируйте `Cookie`: + +=== "Python 3.10+" + + ```Python hl_lines="1" + {!> ../../../docs_src/cookie_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3" + {!> ../../../docs_src/cookie_params/tutorial001.py!} + ``` + +## Объявление параметров `Cookie` + +Затем объявляйте параметры cookie, используя ту же структуру, что и с `Path` и `Query`. + +Первое значение - это значение по умолчанию, вы можете передать все дополнительные параметры проверки или аннотации: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/cookie_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/cookie_params/tutorial001.py!} + ``` + +!!! note "Технические детали" + `Cookie` - это класс, родственный `Path` и `Query`. Он также наследуется от общего класса `Param`. + + Но помните, что когда вы импортируете `Query`, `Path`, `Cookie` и другое из `fastapi`, это фактически функции, которые возвращают специальные классы. + +!!! info "Дополнительная информация" + Для объявления cookies, вам нужно использовать `Cookie`, иначе параметры будут интерпретированы как параметры запроса. + +## Резюме + +Объявляйте cookies с помощью `Cookie`, используя тот же общий шаблон, что и `Query`, и `Path`. diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index f35ee968c..4b9727872 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -45,6 +45,7 @@ nav: - fa: /fa/ - fr: /fr/ - he: /he/ + - hy: /hy/ - id: /id/ - it: /it/ - ja: /ja/ @@ -55,6 +56,7 @@ nav: - ru: /ru/ - sq: /sq/ - sv: /sv/ + - ta: /ta/ - tr: /tr/ - uk: /uk/ - zh: /zh/ @@ -62,12 +64,16 @@ nav: - fastapi-people.md - python-types.md - Учебник - руководство пользователя: + - tutorial/body-fields.md - tutorial/background-tasks.md + - tutorial/cookie-params.md - async.md - Развёртывание: - deployment/index.md - deployment/versions.md +- history-design-future.md - external-links.md +- contributing.md markdown_extensions: - toc: permalink: true @@ -90,7 +96,7 @@ markdown_extensions: extra: analytics: provider: google - property: UA-133183413-1 + property: G-YNEVN69SC3 social: - icon: fontawesome/brands/github-alt link: https://github.com/tiangolo/fastapi @@ -121,6 +127,8 @@ extra: name: fr - français - link: /he/ name: he + - link: /hy/ + name: hy - link: /id/ name: id - link: /it/ @@ -141,6 +149,8 @@ extra: name: sq - shqip - link: /sv/ name: sv - svenska + - link: /ta/ + name: ta - தமிழ் - link: /tr/ name: tr - Türkçe - link: /uk/ diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml index b07f3bc63..f24c7c503 100644 --- a/docs/sq/mkdocs.yml +++ b/docs/sq/mkdocs.yml @@ -45,6 +45,7 @@ nav: - fa: /fa/ - fr: /fr/ - he: /he/ + - hy: /hy/ - id: /id/ - it: /it/ - ja: /ja/ @@ -55,6 +56,7 @@ nav: - ru: /ru/ - sq: /sq/ - sv: /sv/ + - ta: /ta/ - tr: /tr/ - uk: /uk/ - zh: /zh/ @@ -80,7 +82,7 @@ markdown_extensions: extra: analytics: provider: google - property: UA-133183413-1 + property: G-YNEVN69SC3 social: - icon: fontawesome/brands/github-alt link: https://github.com/tiangolo/fastapi @@ -111,6 +113,8 @@ extra: name: fr - français - link: /he/ name: he + - link: /hy/ + name: hy - link: /id/ name: id - link: /it/ @@ -131,6 +135,8 @@ extra: name: sq - shqip - link: /sv/ name: sv - svenska + - link: /ta/ + name: ta - தமிழ் - link: /tr/ name: tr - Türkçe - link: /uk/ diff --git a/docs/sv/mkdocs.yml b/docs/sv/mkdocs.yml index 3332d232d..574cc5abd 100644 --- a/docs/sv/mkdocs.yml +++ b/docs/sv/mkdocs.yml @@ -45,6 +45,7 @@ nav: - fa: /fa/ - fr: /fr/ - he: /he/ + - hy: /hy/ - id: /id/ - it: /it/ - ja: /ja/ @@ -55,6 +56,7 @@ nav: - ru: /ru/ - sq: /sq/ - sv: /sv/ + - ta: /ta/ - tr: /tr/ - uk: /uk/ - zh: /zh/ @@ -80,7 +82,7 @@ markdown_extensions: extra: analytics: provider: google - property: UA-133183413-1 + property: G-YNEVN69SC3 social: - icon: fontawesome/brands/github-alt link: https://github.com/tiangolo/fastapi @@ -111,6 +113,8 @@ extra: name: fr - français - link: /he/ name: he + - link: /hy/ + name: hy - link: /id/ name: id - link: /it/ @@ -131,6 +135,8 @@ extra: name: sq - shqip - link: /sv/ name: sv - svenska + - link: /ta/ + name: ta - தமிழ் - link: /tr/ name: tr - Türkçe - link: /uk/ diff --git a/docs/ta/mkdocs.yml b/docs/ta/mkdocs.yml new file mode 100644 index 000000000..e31821e4b --- /dev/null +++ b/docs/ta/mkdocs.yml @@ -0,0 +1,148 @@ +site_name: FastAPI +site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production +site_url: https://fastapi.tiangolo.com/ta/ +theme: + name: material + custom_dir: overrides + palette: + - media: '(prefers-color-scheme: light)' + scheme: default + primary: teal + accent: amber + toggle: + icon: material/lightbulb + name: Switch to light mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: teal + accent: amber + toggle: + icon: material/lightbulb-outline + name: Switch to dark mode + features: + - search.suggest + - search.highlight + - content.tabs.link + icon: + repo: fontawesome/brands/github-alt + logo: https://fastapi.tiangolo.com/img/icon-white.svg + favicon: https://fastapi.tiangolo.com/img/favicon.png + language: en +repo_name: tiangolo/fastapi +repo_url: https://github.com/tiangolo/fastapi +edit_uri: '' +plugins: +- search +- markdownextradata: + data: data +nav: +- FastAPI: index.md +- Languages: + - en: / + - az: /az/ + - de: /de/ + - es: /es/ + - fa: /fa/ + - fr: /fr/ + - he: /he/ + - id: /id/ + - it: /it/ + - ja: /ja/ + - ko: /ko/ + - nl: /nl/ + - pl: /pl/ + - pt: /pt/ + - ru: /ru/ + - sq: /sq/ + - sv: /sv/ + - ta: /ta/ + - tr: /tr/ + - uk: /uk/ + - zh: /zh/ +markdown_extensions: +- toc: + permalink: true +- markdown.extensions.codehilite: + guess_lang: false +- mdx_include: + base_path: docs +- admonition +- codehilite +- extra +- pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format '' +- pymdownx.tabbed: + alternate_style: true +- attr_list +- md_in_html +extra: + analytics: + provider: google + property: UA-133183413-1 + social: + - icon: fontawesome/brands/github-alt + link: https://github.com/tiangolo/fastapi + - icon: fontawesome/brands/discord + link: https://discord.gg/VQjSZaeJmf + - icon: fontawesome/brands/twitter + link: https://twitter.com/fastapi + - icon: fontawesome/brands/linkedin + link: https://www.linkedin.com/in/tiangolo + - icon: fontawesome/brands/dev + link: https://dev.to/tiangolo + - icon: fontawesome/brands/medium + link: https://medium.com/@tiangolo + - icon: fontawesome/solid/globe + link: https://tiangolo.com + alternate: + - link: / + name: en - English + - link: /az/ + name: az + - link: /de/ + name: de + - link: /es/ + name: es - español + - link: /fa/ + name: fa + - link: /fr/ + name: fr - français + - link: /he/ + name: he + - link: /id/ + name: id + - link: /it/ + name: it - italiano + - link: /ja/ + name: ja - 日本語 + - link: /ko/ + name: ko - 한국어 + - link: /nl/ + name: nl + - link: /pl/ + name: pl + - link: /pt/ + name: pt - português + - link: /ru/ + name: ru - русский язык + - link: /sq/ + name: sq - shqip + - link: /sv/ + name: sv - svenska + - link: /ta/ + name: ta - தமிழ் + - link: /tr/ + name: tr - Türkçe + - link: /uk/ + name: uk - українська мова + - link: /zh/ + name: zh - 汉语 +extra_css: +- https://fastapi.tiangolo.com/css/termynal.css +- https://fastapi.tiangolo.com/css/custom.css +extra_javascript: +- https://fastapi.tiangolo.com/js/termynal.js +- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/ta/overrides/.gitignore b/docs/ta/overrides/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tr/docs/tutorial/first_steps.md b/docs/tr/docs/tutorial/first_steps.md new file mode 100644 index 000000000..b39802f5d --- /dev/null +++ b/docs/tr/docs/tutorial/first_steps.md @@ -0,0 +1,336 @@ +# İlk Adımlar + +En basit FastAPI dosyası şu şekildedir: + +```Python +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Bunu bir `main.py` dosyasına kopyalayın. + +Projeyi çalıştırın: + +
get
işlemi kullanılarak
+
+
+!!! info "`@decorator` Bilgisi"
+ Python `@something` şeklinde ifadeleri "decorator" olarak adlandırır.
+
+ Decoratoru bir fonksiyonun üzerine koyarsınız. Dekoratif bir şapka gibi (Sanırım terim buradan gelmektedir).
+
+ Bir "decorator" fonksiyonu alır ve bazı işlemler gerçekleştir.
+
+ Bizim durumumzda decarator **FastAPI'ye** fonksiyonun bir `get` işlemi ile `/` pathine geldiğini söyler.
+
+ Bu **path işlem decoratordür**
+
+Ayrıca diğer işlemleri de kullanabilirsiniz:
+
+* `@app.post()`
+* `@app.put()`
+* `@app.delete()`
+
+Ve daha egzotik olanları:
+
+* `@app.options()`
+* `@app.head()`
+* `@app.patch()`
+* `@app.trace()`
+
+!!! tip
+ Her işlemi (HTTP method) istediğiniz gibi kullanmakta özgürsünüz.
+
+ **FastAPI** herhangi bir özel anlamı zorlamaz.
+
+ Buradaki bilgiler bir gereklilik değil, bir kılavuz olarak sunulmaktadır.
+
+ Örneğin, GraphQL kullanırkan normalde tüm işlemleri yalnızca `POST` işlemini kullanarak gerçekleştirirsiniz.
+
+### Adım 4: **path işlem fonksiyonunu** tanımlayın
+
+Aşağıdakiler bizim **path işlem fonksiyonlarımızdır**:
+
+* **path**: `/`
+* **işlem**: `get`
+* **function**: "decorator"ün altındaki fonksiyondur (`@app.get("/")` altında).
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Bu bir Python fonksiyonudur.
+
+Bir `GET` işlemi kullanarak "`/`" URL'sine bir istek geldiğinde **FastAPI** tarafından çağrılır.
+
+Bu durumda bir `async` fonksiyonudur.
+
+---
+
+Bunu `async def` yerine normal bir fonksiyon olarakta tanımlayabilirsiniz.
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial003.py!}
+```
+
+!!! note
+
+ Eğer farkı bilmiyorsanız, [Async: *"Acelesi var?"*](../async.md#in-a-hurry){.internal-link target=_blank} kontrol edebilirsiniz.
+
+### Adım 5: İçeriği geri döndürün
+
+
+```Python hl_lines="8"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Bir `dict`, `list` döndürebilir veya `str`, `int` gibi tekil değerler döndürebilirsiniz.
+
+Ayrıca, Pydantic modellerini de döndürebilirsiniz. (Bununla ilgili daha sonra ayrıntılı bilgi göreceksiniz.)
+
+Otomatik olarak JSON'a dönüştürülecek(ORM'ler vb. dahil) başka birçok nesne ve model vardır. En beğendiklerinizi kullanmayı deneyin, yüksek ihtimalle destekleniyordur.
+
+## Özet
+
+* `FastAPI`'yi içe aktarın.
+* Bir `app` örneği oluşturun.
+* **path işlem decorator** yazın. (`@app.get("/")` gibi)
+* **path işlem fonksiyonu** yazın. (`def root(): ...` gibi)
+* Development sunucunuzu çalıştırın. (`uvicorn main:app --reload` gibi)
diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml
index e29d25936..19dcf2099 100644
--- a/docs/tr/mkdocs.yml
+++ b/docs/tr/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,12 +56,15 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
- features.md
- fastapi-people.md
- python-types.md
+- Tutorial - User Guide:
+ - tutorial/first-steps.md
markdown_extensions:
- toc:
permalink: true
@@ -83,7 +87,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -114,6 +118,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -134,6 +140,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml
index 711328771..b8152e821 100644
--- a/docs/uk/mkdocs.yml
+++ b/docs/uk/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -80,7 +82,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -111,6 +113,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -131,6 +135,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs/zh/docs/benchmarks.md b/docs/zh/docs/benchmarks.md
index 8991c72cd..71e8d4838 100644
--- a/docs/zh/docs/benchmarks.md
+++ b/docs/zh/docs/benchmarks.md
@@ -1,6 +1,6 @@
# 基准测试
-第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次与 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*)
+第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次于 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*)
但是在查看基准得分和对比时,请注意以下几点。
diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md
index 7901e9c2c..4db3ef10c 100644
--- a/docs/zh/docs/index.md
+++ b/docs/zh/docs/index.md
@@ -28,7 +28,7 @@ FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框
关键特性:
-* **快速**:可与 **NodeJS** 和 **Go** 比肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。
+* **快速**:可与 **NodeJS** 和 **Go** 并肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。
* **高效编码**:提高功能开发速度约 200% 至 300%。*
* **更少 bug**:减少约 40% 的人为(开发者)导致错误。*
diff --git a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md
index 5813272ee..f404820df 100644
--- a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md
+++ b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md
@@ -6,16 +6,16 @@
在前面的例子中, 我们从依赖项 ("可依赖对象") 中返回了一个 `dict`:
-=== "Python 3.6 以及 以上"
+=== "Python 3.10+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
```
-=== "Python 3.10 以及以上"
+=== "Python 3.6+"
- ```Python hl_lines="7"
- {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
```
但是后面我们在路径操作函数的参数 `commons` 中得到了一个 `dict`。
@@ -79,46 +79,46 @@ fluffy = Cat(name="Mr Fluffy")
所以,我们可以将上面的依赖项 "可依赖对象" `common_parameters` 更改为类 `CommonQueryParams`:
-=== "Python 3.6 以及 以上"
-
- ```Python hl_lines="11-15"
- {!> ../../../docs_src/dependencies/tutorial002.py!}
- ```
-
-=== "Python 3.10 以及 以上"
+=== "Python 3.10+"
```Python hl_lines="9-13"
{!> ../../../docs_src/dependencies/tutorial002_py310.py!}
```
-注意用于创建类实例的 `__init__` 方法:
-
-=== "Python 3.6 以及 以上"
+=== "Python 3.6+"
- ```Python hl_lines="12"
+ ```Python hl_lines="11-15"
{!> ../../../docs_src/dependencies/tutorial002.py!}
```
-=== "Python 3.10 以及 以上"
+注意用于创建类实例的 `__init__` 方法:
+
+=== "Python 3.10+"
```Python hl_lines="10"
{!> ../../../docs_src/dependencies/tutorial002_py310.py!}
```
-...它与我们以前的 `common_parameters` 具有相同的参数:
-
-=== "Python 3.6 以及 以上"
+=== "Python 3.6+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
```
-=== "Python 3.10 以及 以上"
+...它与我们以前的 `common_parameters` 具有相同的参数:
+
+=== "Python 3.10+"
```Python hl_lines="6"
{!> ../../../docs_src/dependencies/tutorial001_py310.py!}
```
+=== "Python 3.6+"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
这些参数就是 **FastAPI** 用来 "处理" 依赖项的。
在两个例子下,都有:
@@ -133,16 +133,16 @@ fluffy = Cat(name="Mr Fluffy")
现在,您可以使用这个类来声明你的依赖项了。
-=== "Python 3.6 以及 以上"
+=== "Python 3.10+"
- ```Python hl_lines="19"
- {!> ../../../docs_src/dependencies/tutorial002.py!}
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
```
-=== "Python 3.10 以及 以上"
+=== "Python 3.6+"
- ```Python hl_lines="17"
- {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
```
**FastAPI** 调用 `CommonQueryParams` 类。这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。
@@ -183,16 +183,16 @@ commons = Depends(CommonQueryParams)
..就像:
-=== "Python 3.6 以及 以上"
+=== "Python 3.10+"
- ```Python hl_lines="19"
- {!> ../../../docs_src/dependencies/tutorial003.py!}
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial003_py310.py!}
```
-=== "Python 3.10 以及 以上"
+=== "Python 3.6+"
- ```Python hl_lines="17"
- {!> ../../../docs_src/dependencies/tutorial003_py310.py!}
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial003.py!}
```
但是声明类型是被鼓励的,因为那样你的编辑器就会知道将传递什么作为参数 `commons` ,然后它可以帮助你完成代码,类型检查,等等:
@@ -227,16 +227,16 @@ commons: CommonQueryParams = Depends()
同样的例子看起来像这样:
-=== "Python 3.6 以及 以上"
+=== "Python 3.10+"
- ```Python hl_lines="19"
- {!> ../../../docs_src/dependencies/tutorial004.py!}
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial004_py310.py!}
```
-=== "Python 3.10 以及 以上"
+=== "Python 3.6+"
- ```Python hl_lines="17"
- {!> ../../../docs_src/dependencies/tutorial004_py310.py!}
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial004.py!}
```
... **FastAPI** 会知道怎么处理。
diff --git a/docs/zh/docs/tutorial/encoder.md b/docs/zh/docs/tutorial/encoder.md
index cb813940c..76ed846ce 100644
--- a/docs/zh/docs/tutorial/encoder.md
+++ b/docs/zh/docs/tutorial/encoder.md
@@ -20,16 +20,16 @@
它接收一个对象,比如Pydantic模型,并会返回一个JSON兼容的版本:
-=== "Python 3.6 and above"
+=== "Python 3.10+"
- ```Python hl_lines="5 22"
- {!> ../../../docs_src/encoder/tutorial001.py!}
+ ```Python hl_lines="4 21"
+ {!> ../../../docs_src/encoder/tutorial001_py310.py!}
```
-=== "Python 3.10 and above"
+=== "Python 3.6+"
- ```Python hl_lines="4 21"
- {!> ../../../docs_src/encoder/tutorial001_py310.py!}
+ ```Python hl_lines="5 22"
+ {!> ../../../docs_src/encoder/tutorial001.py!}
```
在这个例子中,它将Pydantic模型转换为`dict`,并将`datetime`转换为`str`。
diff --git a/docs/zh/docs/tutorial/request-files.md b/docs/zh/docs/tutorial/request-files.md
index e18d6fc9f..03474907e 100644
--- a/docs/zh/docs/tutorial/request-files.md
+++ b/docs/zh/docs/tutorial/request-files.md
@@ -124,16 +124,16 @@ contents = myfile.file.read()
您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选:
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="9 17"
- {!> ../../../docs_src/request_files/tutorial001_02.py!}
+ ```Python hl_lines="7 14"
+ {!> ../../../docs_src/request_files/tutorial001_02_py310.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="7 14"
- {!> ../../../docs_src/request_files/tutorial001_02_py310.py!}
+ ```Python hl_lines="9 17"
+ {!> ../../../docs_src/request_files/tutorial001_02.py!}
```
## 带有额外元数据的 `UploadFile`
@@ -152,16 +152,16 @@ FastAPI 支持同时上传多个文件。
上传多个文件时,要声明含 `bytes` 或 `UploadFile` 的列表(`List`):
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="10 15"
- {!> ../../../docs_src/request_files/tutorial002.py!}
+ ```Python hl_lines="8 13"
+ {!> ../../../docs_src/request_files/tutorial002_py39.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="8 13"
- {!> ../../../docs_src/request_files/tutorial002_py39.py!}
+ ```Python hl_lines="10 15"
+ {!> ../../../docs_src/request_files/tutorial002.py!}
```
接收的也是含 `bytes` 或 `UploadFile` 的列表(`list`)。
@@ -177,16 +177,16 @@ FastAPI 支持同时上传多个文件。
和之前的方式一样, 您可以为 `File()` 设置额外参数, 即使是 `UploadFile`:
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="18"
- {!> ../../../docs_src/request_files/tutorial003.py!}
+ ```Python hl_lines="16"
+ {!> ../../../docs_src/request_files/tutorial003_py39.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="16"
- {!> ../../../docs_src/request_files/tutorial003_py39.py!}
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/request_files/tutorial003.py!}
```
## 小结
diff --git a/docs/zh/docs/tutorial/sql-databases.md b/docs/zh/docs/tutorial/sql-databases.md
index 6b354c2b6..482588f94 100644
--- a/docs/zh/docs/tutorial/sql-databases.md
+++ b/docs/zh/docs/tutorial/sql-databases.md
@@ -246,22 +246,22 @@ connect_args={"check_same_thread": False}
但是为了安全起见,`password`不会出现在其他同类 Pydantic*模型*中,例如用户请求时不应该从 API 返回响应中包含它。
-=== "Python 3.6 及以上版本"
+=== "Python 3.10+"
- ```Python hl_lines="3 6-8 11-12 23-24 27-28"
- {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
+ ```Python hl_lines="1 4-6 9-10 21-22 25-26"
+ {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.9+"
```Python hl_lines="3 6-8 11-12 23-24 27-28"
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!}
```
-=== "Python 3.10 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="1 4-6 9-10 21-22 25-26"
- {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
+ ```Python hl_lines="3 6-8 11-12 23-24 27-28"
+ {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
```
#### SQLAlchemy 风格和 Pydantic 风格
@@ -290,22 +290,22 @@ name: str
不仅是这些项目的 ID,还有我们在 Pydantic*模型*中定义的用于读取项目的所有数据:`Item`.
-=== "Python 3.6 及以上版本"
+=== "Python 3.10+"
- ```Python hl_lines="15-17 31-34"
- {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
+ ```Python hl_lines="13-15 29-32"
+ {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.9+"
```Python hl_lines="15-17 31-34"
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!}
```
-=== "Python 3.10 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="13-15 29-32"
- {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
+ ```Python hl_lines="15-17 31-34"
+ {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
```
!!! tip
@@ -319,22 +319,22 @@ name: str
在`Config`类中,设置属性`orm_mode = True`。
-=== "Python 3.6 及以上版本"
+=== "Python 3.10+"
- ```Python hl_lines="15 19-20 31 36-37"
- {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
+ ```Python hl_lines="13 17-18 29 34-35"
+ {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.9+"
```Python hl_lines="15 19-20 31 36-37"
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!}
```
-=== "Python 3.10 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="13 17-18 29 34-35"
- {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
+ ```Python hl_lines="15 19-20 31 36-37"
+ {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
```
!!! tip
@@ -465,16 +465,16 @@ current_user.items
以非常简单的方式创建数据库表:
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="9"
- {!> ../../../docs_src/sql_databases/sql_app/main.py!}
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="7"
- {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/sql_databases/sql_app/main.py!}
```
#### Alembic 注意
@@ -499,16 +499,16 @@ current_user.items
我们的依赖项将创建一个新的 SQLAlchemy `SessionLocal`,它将在单个请求中使用,然后在请求完成后关闭它。
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="15-20"
- {!> ../../../docs_src/sql_databases/sql_app/main.py!}
+ ```Python hl_lines="13-18"
+ {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="13-18"
- {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
+ ```Python hl_lines="15-20"
+ {!> ../../../docs_src/sql_databases/sql_app/main.py!}
```
!!! info
@@ -524,16 +524,16 @@ current_user.items
*这将为我们在路径操作函数*中提供更好的编辑器支持,因为编辑器将知道`db`参数的类型`Session`:
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="24 32 38 47 53"
- {!> ../../../docs_src/sql_databases/sql_app/main.py!}
+ ```Python hl_lines="22 30 36 45 51"
+ {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="22 30 36 45 51"
- {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
+ ```Python hl_lines="24 32 38 47 53"
+ {!> ../../../docs_src/sql_databases/sql_app/main.py!}
```
!!! info "技术细节"
@@ -545,16 +545,16 @@ current_user.items
现在,到了最后,编写标准的**FastAPI** *路径操作*代码。
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="23-28 31-34 37-42 45-49 52-55"
- {!> ../../../docs_src/sql_databases/sql_app/main.py!}
+ ```Python hl_lines="21-26 29-32 35-40 43-47 50-53"
+ {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="21-26 29-32 35-40 43-47 50-53"
- {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
+ ```Python hl_lines="23-28 31-34 37-42 45-49 52-55"
+ {!> ../../../docs_src/sql_databases/sql_app/main.py!}
```
我们在依赖项中的每个请求之前利用`yield`创建数据库会话,然后关闭它。
@@ -638,22 +638,22 @@ def read_user(user_id: int, db: Session = Depends(get_db)):
* `sql_app/schemas.py`:
-=== "Python 3.6 及以上版本"
+=== "Python 3.10+"
```Python
- {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
+ {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.9+"
```Python
{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!}
```
-=== "Python 3.10 及以上版本"
+=== "Python 3.6+"
```Python
- {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!}
+ {!> ../../../docs_src/sql_databases/sql_app/schemas.py!}
```
* `sql_app/crud.py`:
@@ -664,16 +664,16 @@ def read_user(user_id: int, db: Session = Depends(get_db)):
* `sql_app/main.py`:
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
```Python
- {!> ../../../docs_src/sql_databases/sql_app/main.py!}
+ {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
```Python
- {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!}
+ {!> ../../../docs_src/sql_databases/sql_app/main.py!}
```
## 执行项目
@@ -723,16 +723,16 @@ $ uvicorn sql_app.main:app --reload
我们将添加中间件(只是一个函数)将为每个请求创建一个新的 SQLAlchemy`SessionLocal`,将其添加到请求中,然后在请求完成后关闭它。
-=== "Python 3.6 及以上版本"
+=== "Python 3.9+"
- ```Python hl_lines="14-22"
- {!> ../../../docs_src/sql_databases/sql_app/alt_main.py!}
+ ```Python hl_lines="12-20"
+ {!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!}
```
-=== "Python 3.9 及以上版本"
+=== "Python 3.6+"
- ```Python hl_lines="12-20"
- {!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!}
+ ```Python hl_lines="14-22"
+ {!> ../../../docs_src/sql_databases/sql_app/alt_main.py!}
```
!!! info
diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml
index f4c3c0ec1..d25881c43 100644
--- a/docs/zh/mkdocs.yml
+++ b/docs/zh/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- fa: /fa/
- fr: /fr/
- he: /he/
+ - hy: /hy/
- id: /id/
- it: /it/
- ja: /ja/
@@ -55,6 +56,7 @@ nav:
- ru: /ru/
- sq: /sq/
- sv: /sv/
+ - ta: /ta/
- tr: /tr/
- uk: /uk/
- zh: /zh/
@@ -137,7 +139,7 @@ markdown_extensions:
extra:
analytics:
provider: google
- property: UA-133183413-1
+ property: G-YNEVN69SC3
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
@@ -168,6 +170,8 @@ extra:
name: fr - français
- link: /he/
name: he
+ - link: /hy/
+ name: hy
- link: /id/
name: id
- link: /it/
@@ -188,6 +192,8 @@ extra:
name: sq - shqip
- link: /sv/
name: sv - svenska
+ - link: /ta/
+ name: ta - தமிழ்
- link: /tr/
name: tr - Türkçe
- link: /uk/
diff --git a/docs_src/additional_status_codes/tutorial001_an.py b/docs_src/additional_status_codes/tutorial001_an.py
new file mode 100644
index 000000000..b5ad6a16b
--- /dev/null
+++ b/docs_src/additional_status_codes/tutorial001_an.py
@@ -0,0 +1,26 @@
+from typing import Union
+
+from fastapi import Body, FastAPI, status
+from fastapi.responses import JSONResponse
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
+
+
+@app.put("/items/{item_id}")
+async def upsert_item(
+ item_id: str,
+ name: Annotated[Union[str, None], Body()] = None,
+ size: Annotated[Union[int, None], Body()] = None,
+):
+ if item_id in items:
+ item = items[item_id]
+ item["name"] = name
+ item["size"] = size
+ return item
+ else:
+ item = {"name": name, "size": size}
+ items[item_id] = item
+ return JSONResponse(status_code=status.HTTP_201_CREATED, content=item)
diff --git a/docs_src/additional_status_codes/tutorial001_an_py310.py b/docs_src/additional_status_codes/tutorial001_an_py310.py
new file mode 100644
index 000000000..1865074a1
--- /dev/null
+++ b/docs_src/additional_status_codes/tutorial001_an_py310.py
@@ -0,0 +1,25 @@
+from typing import Annotated
+
+from fastapi import Body, FastAPI, status
+from fastapi.responses import JSONResponse
+
+app = FastAPI()
+
+items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
+
+
+@app.put("/items/{item_id}")
+async def upsert_item(
+ item_id: str,
+ name: Annotated[str | None, Body()] = None,
+ size: Annotated[int | None, Body()] = None,
+):
+ if item_id in items:
+ item = items[item_id]
+ item["name"] = name
+ item["size"] = size
+ return item
+ else:
+ item = {"name": name, "size": size}
+ items[item_id] = item
+ return JSONResponse(status_code=status.HTTP_201_CREATED, content=item)
diff --git a/docs_src/additional_status_codes/tutorial001_an_py39.py b/docs_src/additional_status_codes/tutorial001_an_py39.py
new file mode 100644
index 000000000..89653dd8a
--- /dev/null
+++ b/docs_src/additional_status_codes/tutorial001_an_py39.py
@@ -0,0 +1,25 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI, status
+from fastapi.responses import JSONResponse
+
+app = FastAPI()
+
+items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
+
+
+@app.put("/items/{item_id}")
+async def upsert_item(
+ item_id: str,
+ name: Annotated[Union[str, None], Body()] = None,
+ size: Annotated[Union[int, None], Body()] = None,
+):
+ if item_id in items:
+ item = items[item_id]
+ item["name"] = name
+ item["size"] = size
+ return item
+ else:
+ item = {"name": name, "size": size}
+ items[item_id] = item
+ return JSONResponse(status_code=status.HTTP_201_CREATED, content=item)
diff --git a/docs_src/additional_status_codes/tutorial001_py310.py b/docs_src/additional_status_codes/tutorial001_py310.py
new file mode 100644
index 000000000..38bfa455b
--- /dev/null
+++ b/docs_src/additional_status_codes/tutorial001_py310.py
@@ -0,0 +1,23 @@
+from fastapi import Body, FastAPI, status
+from fastapi.responses import JSONResponse
+
+app = FastAPI()
+
+items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
+
+
+@app.put("/items/{item_id}")
+async def upsert_item(
+ item_id: str,
+ name: str | None = Body(default=None),
+ size: int | None = Body(default=None),
+):
+ if item_id in items:
+ item = items[item_id]
+ item["name"] = name
+ item["size"] = size
+ return item
+ else:
+ item = {"name": name, "size": size}
+ items[item_id] = item
+ return JSONResponse(status_code=status.HTTP_201_CREATED, content=item)
diff --git a/docs_src/app_testing/app_b_an/__init__.py b/docs_src/app_testing/app_b_an/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/app_testing/app_b_an/main.py b/docs_src/app_testing/app_b_an/main.py
new file mode 100644
index 000000000..c63134fc9
--- /dev/null
+++ b/docs_src/app_testing/app_b_an/main.py
@@ -0,0 +1,39 @@
+from typing import Union
+
+from fastapi import FastAPI, Header, HTTPException
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+fake_secret_token = "coneofsilence"
+
+fake_db = {
+ "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
+ "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
+}
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ id: str
+ title: str
+ description: Union[str, None] = None
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_main(item_id: str, x_token: Annotated[str, Header()]):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item_id not in fake_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return fake_db[item_id]
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item, x_token: Annotated[str, Header()]):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item.id in fake_db:
+ raise HTTPException(status_code=400, detail="Item already exists")
+ fake_db[item.id] = item
+ return item
diff --git a/docs_src/app_testing/app_b_an/test_main.py b/docs_src/app_testing/app_b_an/test_main.py
new file mode 100644
index 000000000..d186b8ecb
--- /dev/null
+++ b/docs_src/app_testing/app_b_an/test_main.py
@@ -0,0 +1,65 @@
+from fastapi.testclient import TestClient
+
+from .main import app
+
+client = TestClient(app)
+
+
+def test_read_item():
+ response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foo",
+ "title": "Foo",
+ "description": "There goes my hero",
+ }
+
+
+def test_read_item_bad_token():
+ response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_read_inexistent_item():
+ response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 404
+ assert response.json() == {"detail": "Item not found"}
+
+
+def test_create_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foobar",
+ "title": "Foo Bar",
+ "description": "The Foo Barters",
+ }
+
+
+def test_create_item_bad_token():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "hailhydra"},
+ json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_create_existing_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={
+ "id": "foo",
+ "title": "The Foo ID Stealers",
+ "description": "There goes my stealer",
+ },
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/app_testing/app_b_an_py310/__init__.py b/docs_src/app_testing/app_b_an_py310/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/app_testing/app_b_an_py310/main.py b/docs_src/app_testing/app_b_an_py310/main.py
new file mode 100644
index 000000000..48c27a0b8
--- /dev/null
+++ b/docs_src/app_testing/app_b_an_py310/main.py
@@ -0,0 +1,38 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Header, HTTPException
+from pydantic import BaseModel
+
+fake_secret_token = "coneofsilence"
+
+fake_db = {
+ "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
+ "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
+}
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ id: str
+ title: str
+ description: str | None = None
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_main(item_id: str, x_token: Annotated[str, Header()]):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item_id not in fake_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return fake_db[item_id]
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item, x_token: Annotated[str, Header()]):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item.id in fake_db:
+ raise HTTPException(status_code=400, detail="Item already exists")
+ fake_db[item.id] = item
+ return item
diff --git a/docs_src/app_testing/app_b_an_py310/test_main.py b/docs_src/app_testing/app_b_an_py310/test_main.py
new file mode 100644
index 000000000..d186b8ecb
--- /dev/null
+++ b/docs_src/app_testing/app_b_an_py310/test_main.py
@@ -0,0 +1,65 @@
+from fastapi.testclient import TestClient
+
+from .main import app
+
+client = TestClient(app)
+
+
+def test_read_item():
+ response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foo",
+ "title": "Foo",
+ "description": "There goes my hero",
+ }
+
+
+def test_read_item_bad_token():
+ response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_read_inexistent_item():
+ response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 404
+ assert response.json() == {"detail": "Item not found"}
+
+
+def test_create_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foobar",
+ "title": "Foo Bar",
+ "description": "The Foo Barters",
+ }
+
+
+def test_create_item_bad_token():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "hailhydra"},
+ json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_create_existing_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={
+ "id": "foo",
+ "title": "The Foo ID Stealers",
+ "description": "There goes my stealer",
+ },
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/app_testing/app_b_an_py39/__init__.py b/docs_src/app_testing/app_b_an_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/app_testing/app_b_an_py39/main.py b/docs_src/app_testing/app_b_an_py39/main.py
new file mode 100644
index 000000000..935a510b7
--- /dev/null
+++ b/docs_src/app_testing/app_b_an_py39/main.py
@@ -0,0 +1,38 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Header, HTTPException
+from pydantic import BaseModel
+
+fake_secret_token = "coneofsilence"
+
+fake_db = {
+ "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
+ "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
+}
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ id: str
+ title: str
+ description: Union[str, None] = None
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_main(item_id: str, x_token: Annotated[str, Header()]):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item_id not in fake_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return fake_db[item_id]
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item, x_token: Annotated[str, Header()]):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item.id in fake_db:
+ raise HTTPException(status_code=400, detail="Item already exists")
+ fake_db[item.id] = item
+ return item
diff --git a/docs_src/app_testing/app_b_an_py39/test_main.py b/docs_src/app_testing/app_b_an_py39/test_main.py
new file mode 100644
index 000000000..d186b8ecb
--- /dev/null
+++ b/docs_src/app_testing/app_b_an_py39/test_main.py
@@ -0,0 +1,65 @@
+from fastapi.testclient import TestClient
+
+from .main import app
+
+client = TestClient(app)
+
+
+def test_read_item():
+ response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foo",
+ "title": "Foo",
+ "description": "There goes my hero",
+ }
+
+
+def test_read_item_bad_token():
+ response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_read_inexistent_item():
+ response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 404
+ assert response.json() == {"detail": "Item not found"}
+
+
+def test_create_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foobar",
+ "title": "Foo Bar",
+ "description": "The Foo Barters",
+ }
+
+
+def test_create_item_bad_token():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "hailhydra"},
+ json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_create_existing_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={
+ "id": "foo",
+ "title": "The Foo ID Stealers",
+ "description": "There goes my stealer",
+ },
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/app_testing/tutorial002.py b/docs_src/app_testing/tutorial002.py
index b4a9c0586..71c898b3c 100644
--- a/docs_src/app_testing/tutorial002.py
+++ b/docs_src/app_testing/tutorial002.py
@@ -10,7 +10,7 @@ async def read_main():
return {"msg": "Hello World"}
-@app.websocket_route("/ws")
+@app.websocket("/ws")
async def websocket(websocket: WebSocket):
await websocket.accept()
await websocket.send_json({"msg": "Hello WebSocket"})
diff --git a/docs_src/background_tasks/tutorial002_an.py b/docs_src/background_tasks/tutorial002_an.py
new file mode 100644
index 000000000..f63502b09
--- /dev/null
+++ b/docs_src/background_tasks/tutorial002_an.py
@@ -0,0 +1,27 @@
+from typing import Union
+
+from fastapi import BackgroundTasks, Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+def write_log(message: str):
+ with open("log.txt", mode="a") as log:
+ log.write(message)
+
+
+def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None):
+ if q:
+ message = f"found query: {q}\n"
+ background_tasks.add_task(write_log, message)
+ return q
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(
+ email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
+):
+ message = f"message to {email}\n"
+ background_tasks.add_task(write_log, message)
+ return {"message": "Message sent"}
diff --git a/docs_src/background_tasks/tutorial002_an_py310.py b/docs_src/background_tasks/tutorial002_an_py310.py
new file mode 100644
index 000000000..1fc78fbc9
--- /dev/null
+++ b/docs_src/background_tasks/tutorial002_an_py310.py
@@ -0,0 +1,26 @@
+from typing import Annotated
+
+from fastapi import BackgroundTasks, Depends, FastAPI
+
+app = FastAPI()
+
+
+def write_log(message: str):
+ with open("log.txt", mode="a") as log:
+ log.write(message)
+
+
+def get_query(background_tasks: BackgroundTasks, q: str | None = None):
+ if q:
+ message = f"found query: {q}\n"
+ background_tasks.add_task(write_log, message)
+ return q
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(
+ email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
+):
+ message = f"message to {email}\n"
+ background_tasks.add_task(write_log, message)
+ return {"message": "Message sent"}
diff --git a/docs_src/background_tasks/tutorial002_an_py39.py b/docs_src/background_tasks/tutorial002_an_py39.py
new file mode 100644
index 000000000..bfdd14875
--- /dev/null
+++ b/docs_src/background_tasks/tutorial002_an_py39.py
@@ -0,0 +1,26 @@
+from typing import Annotated, Union
+
+from fastapi import BackgroundTasks, Depends, FastAPI
+
+app = FastAPI()
+
+
+def write_log(message: str):
+ with open("log.txt", mode="a") as log:
+ log.write(message)
+
+
+def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None):
+ if q:
+ message = f"found query: {q}\n"
+ background_tasks.add_task(write_log, message)
+ return q
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(
+ email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
+):
+ message = f"message to {email}\n"
+ background_tasks.add_task(write_log, message)
+ return {"message": "Message sent"}
diff --git a/docs_src/bigger_applications/app_an/__init__.py b/docs_src/bigger_applications/app_an/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/bigger_applications/app_an/dependencies.py b/docs_src/bigger_applications/app_an/dependencies.py
new file mode 100644
index 000000000..1374c54b3
--- /dev/null
+++ b/docs_src/bigger_applications/app_an/dependencies.py
@@ -0,0 +1,12 @@
+from fastapi import Header, HTTPException
+from typing_extensions import Annotated
+
+
+async def get_token_header(x_token: Annotated[str, Header()]):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
+async def get_query_token(token: str):
+ if token != "jessica":
+ raise HTTPException(status_code=400, detail="No Jessica token provided")
diff --git a/docs_src/bigger_applications/app_an/internal/__init__.py b/docs_src/bigger_applications/app_an/internal/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/bigger_applications/app_an/internal/admin.py b/docs_src/bigger_applications/app_an/internal/admin.py
new file mode 100644
index 000000000..99d3da86b
--- /dev/null
+++ b/docs_src/bigger_applications/app_an/internal/admin.py
@@ -0,0 +1,8 @@
+from fastapi import APIRouter
+
+router = APIRouter()
+
+
+@router.post("/")
+async def update_admin():
+ return {"message": "Admin getting schwifty"}
diff --git a/docs_src/bigger_applications/app_an/main.py b/docs_src/bigger_applications/app_an/main.py
new file mode 100644
index 000000000..ae544a3aa
--- /dev/null
+++ b/docs_src/bigger_applications/app_an/main.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+from .dependencies import get_query_token, get_token_header
+from .internal import admin
+from .routers import items, users
+
+app = FastAPI(dependencies=[Depends(get_query_token)])
+
+
+app.include_router(users.router)
+app.include_router(items.router)
+app.include_router(
+ admin.router,
+ prefix="/admin",
+ tags=["admin"],
+ dependencies=[Depends(get_token_header)],
+ responses={418: {"description": "I'm a teapot"}},
+)
+
+
+@app.get("/")
+async def root():
+ return {"message": "Hello Bigger Applications!"}
diff --git a/docs_src/bigger_applications/app_an/routers/__init__.py b/docs_src/bigger_applications/app_an/routers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/bigger_applications/app_an/routers/items.py b/docs_src/bigger_applications/app_an/routers/items.py
new file mode 100644
index 000000000..bde9ff4d5
--- /dev/null
+++ b/docs_src/bigger_applications/app_an/routers/items.py
@@ -0,0 +1,38 @@
+from fastapi import APIRouter, Depends, HTTPException
+
+from ..dependencies import get_token_header
+
+router = APIRouter(
+ prefix="/items",
+ tags=["items"],
+ dependencies=[Depends(get_token_header)],
+ responses={404: {"description": "Not found"}},
+)
+
+
+fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
+
+
+@router.get("/")
+async def read_items():
+ return fake_items_db
+
+
+@router.get("/{item_id}")
+async def read_item(item_id: str):
+ if item_id not in fake_items_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
+
+
+@router.put(
+ "/{item_id}",
+ tags=["custom"],
+ responses={403: {"description": "Operation forbidden"}},
+)
+async def update_item(item_id: str):
+ if item_id != "plumbus":
+ raise HTTPException(
+ status_code=403, detail="You can only update the item: plumbus"
+ )
+ return {"item_id": item_id, "name": "The great Plumbus"}
diff --git a/docs_src/bigger_applications/app_an/routers/users.py b/docs_src/bigger_applications/app_an/routers/users.py
new file mode 100644
index 000000000..39b3d7e7c
--- /dev/null
+++ b/docs_src/bigger_applications/app_an/routers/users.py
@@ -0,0 +1,18 @@
+from fastapi import APIRouter
+
+router = APIRouter()
+
+
+@router.get("/users/", tags=["users"])
+async def read_users():
+ return [{"username": "Rick"}, {"username": "Morty"}]
+
+
+@router.get("/users/me", tags=["users"])
+async def read_user_me():
+ return {"username": "fakecurrentuser"}
+
+
+@router.get("/users/{username}", tags=["users"])
+async def read_user(username: str):
+ return {"username": username}
diff --git a/docs_src/bigger_applications/app_an_py39/__init__.py b/docs_src/bigger_applications/app_an_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/bigger_applications/app_an_py39/dependencies.py b/docs_src/bigger_applications/app_an_py39/dependencies.py
new file mode 100644
index 000000000..5c7612aa0
--- /dev/null
+++ b/docs_src/bigger_applications/app_an_py39/dependencies.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import Header, HTTPException
+
+
+async def get_token_header(x_token: Annotated[str, Header()]):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
+async def get_query_token(token: str):
+ if token != "jessica":
+ raise HTTPException(status_code=400, detail="No Jessica token provided")
diff --git a/docs_src/bigger_applications/app_an_py39/internal/__init__.py b/docs_src/bigger_applications/app_an_py39/internal/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/bigger_applications/app_an_py39/internal/admin.py b/docs_src/bigger_applications/app_an_py39/internal/admin.py
new file mode 100644
index 000000000..99d3da86b
--- /dev/null
+++ b/docs_src/bigger_applications/app_an_py39/internal/admin.py
@@ -0,0 +1,8 @@
+from fastapi import APIRouter
+
+router = APIRouter()
+
+
+@router.post("/")
+async def update_admin():
+ return {"message": "Admin getting schwifty"}
diff --git a/docs_src/bigger_applications/app_an_py39/main.py b/docs_src/bigger_applications/app_an_py39/main.py
new file mode 100644
index 000000000..ae544a3aa
--- /dev/null
+++ b/docs_src/bigger_applications/app_an_py39/main.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+from .dependencies import get_query_token, get_token_header
+from .internal import admin
+from .routers import items, users
+
+app = FastAPI(dependencies=[Depends(get_query_token)])
+
+
+app.include_router(users.router)
+app.include_router(items.router)
+app.include_router(
+ admin.router,
+ prefix="/admin",
+ tags=["admin"],
+ dependencies=[Depends(get_token_header)],
+ responses={418: {"description": "I'm a teapot"}},
+)
+
+
+@app.get("/")
+async def root():
+ return {"message": "Hello Bigger Applications!"}
diff --git a/docs_src/bigger_applications/app_an_py39/routers/__init__.py b/docs_src/bigger_applications/app_an_py39/routers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/bigger_applications/app_an_py39/routers/items.py b/docs_src/bigger_applications/app_an_py39/routers/items.py
new file mode 100644
index 000000000..bde9ff4d5
--- /dev/null
+++ b/docs_src/bigger_applications/app_an_py39/routers/items.py
@@ -0,0 +1,38 @@
+from fastapi import APIRouter, Depends, HTTPException
+
+from ..dependencies import get_token_header
+
+router = APIRouter(
+ prefix="/items",
+ tags=["items"],
+ dependencies=[Depends(get_token_header)],
+ responses={404: {"description": "Not found"}},
+)
+
+
+fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
+
+
+@router.get("/")
+async def read_items():
+ return fake_items_db
+
+
+@router.get("/{item_id}")
+async def read_item(item_id: str):
+ if item_id not in fake_items_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
+
+
+@router.put(
+ "/{item_id}",
+ tags=["custom"],
+ responses={403: {"description": "Operation forbidden"}},
+)
+async def update_item(item_id: str):
+ if item_id != "plumbus":
+ raise HTTPException(
+ status_code=403, detail="You can only update the item: plumbus"
+ )
+ return {"item_id": item_id, "name": "The great Plumbus"}
diff --git a/docs_src/bigger_applications/app_an_py39/routers/users.py b/docs_src/bigger_applications/app_an_py39/routers/users.py
new file mode 100644
index 000000000..39b3d7e7c
--- /dev/null
+++ b/docs_src/bigger_applications/app_an_py39/routers/users.py
@@ -0,0 +1,18 @@
+from fastapi import APIRouter
+
+router = APIRouter()
+
+
+@router.get("/users/", tags=["users"])
+async def read_users():
+ return [{"username": "Rick"}, {"username": "Morty"}]
+
+
+@router.get("/users/me", tags=["users"])
+async def read_user_me():
+ return {"username": "fakecurrentuser"}
+
+
+@router.get("/users/{username}", tags=["users"])
+async def read_user(username: str):
+ return {"username": username}
diff --git a/docs_src/body_fields/tutorial001_an.py b/docs_src/body_fields/tutorial001_an.py
new file mode 100644
index 000000000..15ea1b53d
--- /dev/null
+++ b/docs_src/body_fields/tutorial001_an.py
@@ -0,0 +1,22 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel, Field
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = Field(
+ default=None, title="The description of the item", max_length=300
+ )
+ price: float = Field(gt=0, description="The price must be greater than zero")
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_fields/tutorial001_an_py310.py b/docs_src/body_fields/tutorial001_an_py310.py
new file mode 100644
index 000000000..c9d99e1c2
--- /dev/null
+++ b/docs_src/body_fields/tutorial001_an_py310.py
@@ -0,0 +1,21 @@
+from typing import Annotated
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = Field(
+ default=None, title="The description of the item", max_length=300
+ )
+ price: float = Field(gt=0, description="The price must be greater than zero")
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_fields/tutorial001_an_py39.py b/docs_src/body_fields/tutorial001_an_py39.py
new file mode 100644
index 000000000..6ef14470c
--- /dev/null
+++ b/docs_src/body_fields/tutorial001_an_py39.py
@@ -0,0 +1,21 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = Field(
+ default=None, title="The description of the item", max_length=300
+ )
+ price: float = Field(gt=0, description="The price must be greater than zero")
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial001_an.py b/docs_src/body_multiple_params/tutorial001_an.py
new file mode 100644
index 000000000..308eee854
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial001_an.py
@@ -0,0 +1,28 @@
+from typing import Union
+
+from fastapi import FastAPI, Path
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
+ q: Union[str, None] = None,
+ item: Union[Item, None] = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ if item:
+ results.update({"item": item})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial001_an_py310.py b/docs_src/body_multiple_params/tutorial001_an_py310.py
new file mode 100644
index 000000000..2d79c19c2
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial001_an_py310.py
@@ -0,0 +1,27 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Path
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
+ q: str | None = None,
+ item: Item | None = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ if item:
+ results.update({"item": item})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial001_an_py39.py b/docs_src/body_multiple_params/tutorial001_an_py39.py
new file mode 100644
index 000000000..1c0ac3a7b
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial001_an_py39.py
@@ -0,0 +1,27 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Path
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
+ q: Union[str, None] = None,
+ item: Union[Item, None] = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ if item:
+ results.update({"item": item})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial003_an.py b/docs_src/body_multiple_params/tutorial003_an.py
new file mode 100644
index 000000000..39ef7340a
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial003_an.py
@@ -0,0 +1,27 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: Union[str, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial003_an_py310.py b/docs_src/body_multiple_params/tutorial003_an_py310.py
new file mode 100644
index 000000000..593ff893c
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial003_an_py310.py
@@ -0,0 +1,26 @@
+from typing import Annotated
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial003_an_py39.py b/docs_src/body_multiple_params/tutorial003_an_py39.py
new file mode 100644
index 000000000..042351e0b
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial003_an_py39.py
@@ -0,0 +1,26 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: Union[str, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial004.py b/docs_src/body_multiple_params/tutorial004.py
index beea7d1e3..8ce4c7a97 100644
--- a/docs_src/body_multiple_params/tutorial004.py
+++ b/docs_src/body_multiple_params/tutorial004.py
@@ -25,7 +25,7 @@ async def update_item(
item: Item,
user: User,
importance: int = Body(gt=0),
- q: Union[str, None] = None
+ q: Union[str, None] = None,
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
if q:
diff --git a/docs_src/body_multiple_params/tutorial004_an.py b/docs_src/body_multiple_params/tutorial004_an.py
new file mode 100644
index 000000000..f6830f392
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial004_an.py
@@ -0,0 +1,34 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: Union[str, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item,
+ user: User,
+ importance: Annotated[int, Body(gt=0)],
+ q: Union[str, None] = None,
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial004_an_py310.py b/docs_src/body_multiple_params/tutorial004_an_py310.py
new file mode 100644
index 000000000..cd5c66fef
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial004_an_py310.py
@@ -0,0 +1,33 @@
+from typing import Annotated
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item,
+ user: User,
+ importance: Annotated[int, Body(gt=0)],
+ q: str | None = None,
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial004_an_py39.py b/docs_src/body_multiple_params/tutorial004_an_py39.py
new file mode 100644
index 000000000..567427c03
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial004_an_py39.py
@@ -0,0 +1,33 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: Union[str, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item,
+ user: User,
+ importance: Annotated[int, Body(gt=0)],
+ q: Union[str, None] = None,
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial004_py310.py b/docs_src/body_multiple_params/tutorial004_py310.py
index 6d495d408..14acbc3b4 100644
--- a/docs_src/body_multiple_params/tutorial004_py310.py
+++ b/docs_src/body_multiple_params/tutorial004_py310.py
@@ -23,7 +23,7 @@ async def update_item(
item: Item,
user: User,
importance: int = Body(gt=0),
- q: str | None = None
+ q: str | None = None,
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
if q:
diff --git a/docs_src/body_multiple_params/tutorial005_an.py b/docs_src/body_multiple_params/tutorial005_an.py
new file mode 100644
index 000000000..dadde80b5
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial005_an.py
@@ -0,0 +1,20 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial005_an_py310.py b/docs_src/body_multiple_params/tutorial005_an_py310.py
new file mode 100644
index 000000000..f97680ba0
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial005_an_py310.py
@@ -0,0 +1,19 @@
+from typing import Annotated
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial005_an_py39.py b/docs_src/body_multiple_params/tutorial005_an_py39.py
new file mode 100644
index 000000000..9a52425c1
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial005_an_py39.py
@@ -0,0 +1,19 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/cookie_params/tutorial001_an.py b/docs_src/cookie_params/tutorial001_an.py
new file mode 100644
index 000000000..6d5931229
--- /dev/null
+++ b/docs_src/cookie_params/tutorial001_an.py
@@ -0,0 +1,11 @@
+from typing import Union
+
+from fastapi import Cookie, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
+ return {"ads_id": ads_id}
diff --git a/docs_src/cookie_params/tutorial001_an_py310.py b/docs_src/cookie_params/tutorial001_an_py310.py
new file mode 100644
index 000000000..69ac683fe
--- /dev/null
+++ b/docs_src/cookie_params/tutorial001_an_py310.py
@@ -0,0 +1,10 @@
+from typing import Annotated
+
+from fastapi import Cookie, FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
+ return {"ads_id": ads_id}
diff --git a/docs_src/cookie_params/tutorial001_an_py39.py b/docs_src/cookie_params/tutorial001_an_py39.py
new file mode 100644
index 000000000..e18d0a332
--- /dev/null
+++ b/docs_src/cookie_params/tutorial001_an_py39.py
@@ -0,0 +1,10 @@
+from typing import Annotated, Union
+
+from fastapi import Cookie, FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
+ return {"ads_id": ads_id}
diff --git a/docs_src/dependencies/tutorial001_02_an.py b/docs_src/dependencies/tutorial001_02_an.py
new file mode 100644
index 000000000..455d60c82
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_02_an.py
@@ -0,0 +1,25 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+async def common_parameters(
+ q: Union[str, None] = None, skip: int = 0, limit: int = 100
+):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+CommonsDep = Annotated[dict, Depends(common_parameters)]
+
+
+@app.get("/items/")
+async def read_items(commons: CommonsDep):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: CommonsDep):
+ return commons
diff --git a/docs_src/dependencies/tutorial001_02_an_py310.py b/docs_src/dependencies/tutorial001_02_an_py310.py
new file mode 100644
index 000000000..f59637bb2
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_02_an_py310.py
@@ -0,0 +1,22 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+CommonsDep = Annotated[dict, Depends(common_parameters)]
+
+
+@app.get("/items/")
+async def read_items(commons: CommonsDep):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: CommonsDep):
+ return commons
diff --git a/docs_src/dependencies/tutorial001_02_an_py39.py b/docs_src/dependencies/tutorial001_02_an_py39.py
new file mode 100644
index 000000000..df969ae9c
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_02_an_py39.py
@@ -0,0 +1,24 @@
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+async def common_parameters(
+ q: Union[str, None] = None, skip: int = 0, limit: int = 100
+):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+CommonsDep = Annotated[dict, Depends(common_parameters)]
+
+
+@app.get("/items/")
+async def read_items(commons: CommonsDep):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: CommonsDep):
+ return commons
diff --git a/docs_src/dependencies/tutorial001_an.py b/docs_src/dependencies/tutorial001_an.py
new file mode 100644
index 000000000..81e24fe86
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_an.py
@@ -0,0 +1,22 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+async def common_parameters(
+ q: Union[str, None] = None, skip: int = 0, limit: int = 100
+):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
+ return commons
diff --git a/docs_src/dependencies/tutorial001_an_py310.py b/docs_src/dependencies/tutorial001_an_py310.py
new file mode 100644
index 000000000..f2e57ae7b
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_an_py310.py
@@ -0,0 +1,19 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
+ return commons
diff --git a/docs_src/dependencies/tutorial001_an_py39.py b/docs_src/dependencies/tutorial001_an_py39.py
new file mode 100644
index 000000000..5d9fe6ddf
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_an_py39.py
@@ -0,0 +1,21 @@
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+async def common_parameters(
+ q: Union[str, None] = None, skip: int = 0, limit: int = 100
+):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
+ return commons
diff --git a/docs_src/dependencies/tutorial002_an.py b/docs_src/dependencies/tutorial002_an.py
new file mode 100644
index 000000000..964ccf66c
--- /dev/null
+++ b/docs_src/dependencies/tutorial002_an.py
@@ -0,0 +1,26 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial002_an_py310.py b/docs_src/dependencies/tutorial002_an_py310.py
new file mode 100644
index 000000000..077726312
--- /dev/null
+++ b/docs_src/dependencies/tutorial002_an_py310.py
@@ -0,0 +1,25 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial002_an_py39.py b/docs_src/dependencies/tutorial002_an_py39.py
new file mode 100644
index 000000000..844a23c5a
--- /dev/null
+++ b/docs_src/dependencies/tutorial002_an_py39.py
@@ -0,0 +1,25 @@
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial003_an.py b/docs_src/dependencies/tutorial003_an.py
new file mode 100644
index 000000000..ba8e9f717
--- /dev/null
+++ b/docs_src/dependencies/tutorial003_an.py
@@ -0,0 +1,26 @@
+from typing import Any, Union
+
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial003_an_py310.py b/docs_src/dependencies/tutorial003_an_py310.py
new file mode 100644
index 000000000..44116989b
--- /dev/null
+++ b/docs_src/dependencies/tutorial003_an_py310.py
@@ -0,0 +1,25 @@
+from typing import Annotated, Any
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial003_an_py39.py b/docs_src/dependencies/tutorial003_an_py39.py
new file mode 100644
index 000000000..9e9123ad2
--- /dev/null
+++ b/docs_src/dependencies/tutorial003_an_py39.py
@@ -0,0 +1,25 @@
+from typing import Annotated, Any, Union
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial004_an.py b/docs_src/dependencies/tutorial004_an.py
new file mode 100644
index 000000000..78881a354
--- /dev/null
+++ b/docs_src/dependencies/tutorial004_an.py
@@ -0,0 +1,26 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial004_an_py310.py b/docs_src/dependencies/tutorial004_an_py310.py
new file mode 100644
index 000000000..2c009efcc
--- /dev/null
+++ b/docs_src/dependencies/tutorial004_an_py310.py
@@ -0,0 +1,25 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial004_an_py39.py b/docs_src/dependencies/tutorial004_an_py39.py
new file mode 100644
index 000000000..74268626b
--- /dev/null
+++ b/docs_src/dependencies/tutorial004_an_py39.py
@@ -0,0 +1,25 @@
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[CommonQueryParams, Depends()]):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial005_an.py b/docs_src/dependencies/tutorial005_an.py
new file mode 100644
index 000000000..6785099da
--- /dev/null
+++ b/docs_src/dependencies/tutorial005_an.py
@@ -0,0 +1,26 @@
+from typing import Union
+
+from fastapi import Cookie, Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+def query_extractor(q: Union[str, None] = None):
+ return q
+
+
+def query_or_cookie_extractor(
+ q: Annotated[str, Depends(query_extractor)],
+ last_query: Annotated[Union[str, None], Cookie()] = None,
+):
+ if not q:
+ return last_query
+ return q
+
+
+@app.get("/items/")
+async def read_query(
+ query_or_default: Annotated[str, Depends(query_or_cookie_extractor)]
+):
+ return {"q_or_cookie": query_or_default}
diff --git a/docs_src/dependencies/tutorial005_an_py310.py b/docs_src/dependencies/tutorial005_an_py310.py
new file mode 100644
index 000000000..6c0aa0b36
--- /dev/null
+++ b/docs_src/dependencies/tutorial005_an_py310.py
@@ -0,0 +1,25 @@
+from typing import Annotated
+
+from fastapi import Cookie, Depends, FastAPI
+
+app = FastAPI()
+
+
+def query_extractor(q: str | None = None):
+ return q
+
+
+def query_or_cookie_extractor(
+ q: Annotated[str, Depends(query_extractor)],
+ last_query: Annotated[str | None, Cookie()] = None,
+):
+ if not q:
+ return last_query
+ return q
+
+
+@app.get("/items/")
+async def read_query(
+ query_or_default: Annotated[str, Depends(query_or_cookie_extractor)]
+):
+ return {"q_or_cookie": query_or_default}
diff --git a/docs_src/dependencies/tutorial005_an_py39.py b/docs_src/dependencies/tutorial005_an_py39.py
new file mode 100644
index 000000000..e8887e162
--- /dev/null
+++ b/docs_src/dependencies/tutorial005_an_py39.py
@@ -0,0 +1,25 @@
+from typing import Annotated, Union
+
+from fastapi import Cookie, Depends, FastAPI
+
+app = FastAPI()
+
+
+def query_extractor(q: Union[str, None] = None):
+ return q
+
+
+def query_or_cookie_extractor(
+ q: Annotated[str, Depends(query_extractor)],
+ last_query: Annotated[Union[str, None], Cookie()] = None,
+):
+ if not q:
+ return last_query
+ return q
+
+
+@app.get("/items/")
+async def read_query(
+ query_or_default: Annotated[str, Depends(query_or_cookie_extractor)]
+):
+ return {"q_or_cookie": query_or_default}
diff --git a/docs_src/dependencies/tutorial006_an.py b/docs_src/dependencies/tutorial006_an.py
new file mode 100644
index 000000000..5aaea04d1
--- /dev/null
+++ b/docs_src/dependencies/tutorial006_an.py
@@ -0,0 +1,20 @@
+from fastapi import Depends, FastAPI, Header, HTTPException
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+async def verify_token(x_token: Annotated[str, Header()]):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
+async def verify_key(x_key: Annotated[str, Header()]):
+ if x_key != "fake-super-secret-key":
+ raise HTTPException(status_code=400, detail="X-Key header invalid")
+ return x_key
+
+
+@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
+async def read_items():
+ return [{"item": "Foo"}, {"item": "Bar"}]
diff --git a/docs_src/dependencies/tutorial006_an_py39.py b/docs_src/dependencies/tutorial006_an_py39.py
new file mode 100644
index 000000000..11976ed6a
--- /dev/null
+++ b/docs_src/dependencies/tutorial006_an_py39.py
@@ -0,0 +1,21 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, Header, HTTPException
+
+app = FastAPI()
+
+
+async def verify_token(x_token: Annotated[str, Header()]):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
+async def verify_key(x_key: Annotated[str, Header()]):
+ if x_key != "fake-super-secret-key":
+ raise HTTPException(status_code=400, detail="X-Key header invalid")
+ return x_key
+
+
+@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
+async def read_items():
+ return [{"item": "Foo"}, {"item": "Bar"}]
diff --git a/docs_src/dependencies/tutorial008_an.py b/docs_src/dependencies/tutorial008_an.py
new file mode 100644
index 000000000..2de86f042
--- /dev/null
+++ b/docs_src/dependencies/tutorial008_an.py
@@ -0,0 +1,26 @@
+from fastapi import Depends
+from typing_extensions import Annotated
+
+
+async def dependency_a():
+ dep_a = generate_dep_a()
+ try:
+ yield dep_a
+ finally:
+ dep_a.close()
+
+
+async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
+ dep_b = generate_dep_b()
+ try:
+ yield dep_b
+ finally:
+ dep_b.close(dep_a)
+
+
+async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
+ dep_c = generate_dep_c()
+ try:
+ yield dep_c
+ finally:
+ dep_c.close(dep_b)
diff --git a/docs_src/dependencies/tutorial008_an_py39.py b/docs_src/dependencies/tutorial008_an_py39.py
new file mode 100644
index 000000000..acc804c32
--- /dev/null
+++ b/docs_src/dependencies/tutorial008_an_py39.py
@@ -0,0 +1,27 @@
+from typing import Annotated
+
+from fastapi import Depends
+
+
+async def dependency_a():
+ dep_a = generate_dep_a()
+ try:
+ yield dep_a
+ finally:
+ dep_a.close()
+
+
+async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
+ dep_b = generate_dep_b()
+ try:
+ yield dep_b
+ finally:
+ dep_b.close(dep_a)
+
+
+async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
+ dep_c = generate_dep_c()
+ try:
+ yield dep_c
+ finally:
+ dep_c.close(dep_b)
diff --git a/docs_src/dependencies/tutorial011_an.py b/docs_src/dependencies/tutorial011_an.py
new file mode 100644
index 000000000..6c13d9033
--- /dev/null
+++ b/docs_src/dependencies/tutorial011_an.py
@@ -0,0 +1,22 @@
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class FixedContentQueryChecker:
+ def __init__(self, fixed_content: str):
+ self.fixed_content = fixed_content
+
+ def __call__(self, q: str = ""):
+ if q:
+ return self.fixed_content in q
+ return False
+
+
+checker = FixedContentQueryChecker("bar")
+
+
+@app.get("/query-checker/")
+async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]):
+ return {"fixed_content_in_query": fixed_content_included}
diff --git a/docs_src/dependencies/tutorial011_an_py39.py b/docs_src/dependencies/tutorial011_an_py39.py
new file mode 100644
index 000000000..68b7434ec
--- /dev/null
+++ b/docs_src/dependencies/tutorial011_an_py39.py
@@ -0,0 +1,23 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+class FixedContentQueryChecker:
+ def __init__(self, fixed_content: str):
+ self.fixed_content = fixed_content
+
+ def __call__(self, q: str = ""):
+ if q:
+ return self.fixed_content in q
+ return False
+
+
+checker = FixedContentQueryChecker("bar")
+
+
+@app.get("/query-checker/")
+async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]):
+ return {"fixed_content_in_query": fixed_content_included}
diff --git a/docs_src/dependencies/tutorial012_an.py b/docs_src/dependencies/tutorial012_an.py
new file mode 100644
index 000000000..7541e6bf4
--- /dev/null
+++ b/docs_src/dependencies/tutorial012_an.py
@@ -0,0 +1,26 @@
+from fastapi import Depends, FastAPI, Header, HTTPException
+from typing_extensions import Annotated
+
+
+async def verify_token(x_token: Annotated[str, Header()]):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
+async def verify_key(x_key: Annotated[str, Header()]):
+ if x_key != "fake-super-secret-key":
+ raise HTTPException(status_code=400, detail="X-Key header invalid")
+ return x_key
+
+
+app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
+
+
+@app.get("/items/")
+async def read_items():
+ return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
+
+
+@app.get("/users/")
+async def read_users():
+ return [{"username": "Rick"}, {"username": "Morty"}]
diff --git a/docs_src/dependencies/tutorial012_an_py39.py b/docs_src/dependencies/tutorial012_an_py39.py
new file mode 100644
index 000000000..7541e6bf4
--- /dev/null
+++ b/docs_src/dependencies/tutorial012_an_py39.py
@@ -0,0 +1,26 @@
+from fastapi import Depends, FastAPI, Header, HTTPException
+from typing_extensions import Annotated
+
+
+async def verify_token(x_token: Annotated[str, Header()]):
+ if x_token != "fake-super-secret-token":
+ raise HTTPException(status_code=400, detail="X-Token header invalid")
+
+
+async def verify_key(x_key: Annotated[str, Header()]):
+ if x_key != "fake-super-secret-key":
+ raise HTTPException(status_code=400, detail="X-Key header invalid")
+ return x_key
+
+
+app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
+
+
+@app.get("/items/")
+async def read_items():
+ return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
+
+
+@app.get("/users/")
+async def read_users():
+ return [{"username": "Rick"}, {"username": "Morty"}]
diff --git a/docs_src/dependency_testing/tutorial001_an.py b/docs_src/dependency_testing/tutorial001_an.py
new file mode 100644
index 000000000..4c76a87ff
--- /dev/null
+++ b/docs_src/dependency_testing/tutorial001_an.py
@@ -0,0 +1,60 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+async def common_parameters(
+ q: Union[str, None] = None, skip: int = 0, limit: int = 100
+):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
+ return {"message": "Hello Items!", "params": commons}
+
+
+@app.get("/users/")
+async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
+ return {"message": "Hello Users!", "params": commons}
+
+
+client = TestClient(app)
+
+
+async def override_dependency(q: Union[str, None] = None):
+ return {"q": q, "skip": 5, "limit": 10}
+
+
+app.dependency_overrides[common_parameters] = override_dependency
+
+
+def test_override_in_items():
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": None, "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_q():
+ response = client.get("/items/?q=foo")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_params():
+ response = client.get("/items/?q=foo&skip=100&limit=200")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
diff --git a/docs_src/dependency_testing/tutorial001_an_py310.py b/docs_src/dependency_testing/tutorial001_an_py310.py
new file mode 100644
index 000000000..f866e7a1b
--- /dev/null
+++ b/docs_src/dependency_testing/tutorial001_an_py310.py
@@ -0,0 +1,57 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
+ return {"message": "Hello Items!", "params": commons}
+
+
+@app.get("/users/")
+async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
+ return {"message": "Hello Users!", "params": commons}
+
+
+client = TestClient(app)
+
+
+async def override_dependency(q: str | None = None):
+ return {"q": q, "skip": 5, "limit": 10}
+
+
+app.dependency_overrides[common_parameters] = override_dependency
+
+
+def test_override_in_items():
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": None, "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_q():
+ response = client.get("/items/?q=foo")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_params():
+ response = client.get("/items/?q=foo&skip=100&limit=200")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
diff --git a/docs_src/dependency_testing/tutorial001_an_py39.py b/docs_src/dependency_testing/tutorial001_an_py39.py
new file mode 100644
index 000000000..bccb0cdb1
--- /dev/null
+++ b/docs_src/dependency_testing/tutorial001_an_py39.py
@@ -0,0 +1,59 @@
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+async def common_parameters(
+ q: Union[str, None] = None, skip: int = 0, limit: int = 100
+):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
+ return {"message": "Hello Items!", "params": commons}
+
+
+@app.get("/users/")
+async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
+ return {"message": "Hello Users!", "params": commons}
+
+
+client = TestClient(app)
+
+
+async def override_dependency(q: Union[str, None] = None):
+ return {"q": q, "skip": 5, "limit": 10}
+
+
+app.dependency_overrides[common_parameters] = override_dependency
+
+
+def test_override_in_items():
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": None, "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_q():
+ response = client.get("/items/?q=foo")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_params():
+ response = client.get("/items/?q=foo&skip=100&limit=200")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
diff --git a/docs_src/dependency_testing/tutorial001_py310.py b/docs_src/dependency_testing/tutorial001_py310.py
new file mode 100644
index 000000000..d1f6d4374
--- /dev/null
+++ b/docs_src/dependency_testing/tutorial001_py310.py
@@ -0,0 +1,55 @@
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: dict = Depends(common_parameters)):
+ return {"message": "Hello Items!", "params": commons}
+
+
+@app.get("/users/")
+async def read_users(commons: dict = Depends(common_parameters)):
+ return {"message": "Hello Users!", "params": commons}
+
+
+client = TestClient(app)
+
+
+async def override_dependency(q: str | None = None):
+ return {"q": q, "skip": 5, "limit": 10}
+
+
+app.dependency_overrides[common_parameters] = override_dependency
+
+
+def test_override_in_items():
+ response = client.get("/items/")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": None, "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_q():
+ response = client.get("/items/?q=foo")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
+
+
+def test_override_in_items_with_params():
+ response = client.get("/items/?q=foo&skip=100&limit=200")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": "Hello Items!",
+ "params": {"q": "foo", "skip": 5, "limit": 10},
+ }
diff --git a/docs_src/events/tutorial003.py b/docs_src/events/tutorial003.py
new file mode 100644
index 000000000..2b650590b
--- /dev/null
+++ b/docs_src/events/tutorial003.py
@@ -0,0 +1,28 @@
+from contextlib import asynccontextmanager
+
+from fastapi import FastAPI
+
+
+def fake_answer_to_everything_ml_model(x: float):
+ return x * 42
+
+
+ml_models = {}
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ # Load the ML model
+ ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model
+ yield
+ # Clean up the ML models and release the resources
+ ml_models.clear()
+
+
+app = FastAPI(lifespan=lifespan)
+
+
+@app.get("/predict")
+async def predict(x: float):
+ result = ml_models["answer_to_everything"](x)
+ return {"result": result}
diff --git a/docs_src/extra_data_types/tutorial001_an.py b/docs_src/extra_data_types/tutorial001_an.py
new file mode 100644
index 000000000..a4c074241
--- /dev/null
+++ b/docs_src/extra_data_types/tutorial001_an.py
@@ -0,0 +1,29 @@
+from datetime import datetime, time, timedelta
+from typing import Union
+from uuid import UUID
+
+from fastapi import Body, FastAPI
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def read_items(
+ item_id: UUID,
+ start_datetime: Annotated[Union[datetime, None], Body()] = None,
+ end_datetime: Annotated[Union[datetime, None], Body()] = None,
+ repeat_at: Annotated[Union[time, None], Body()] = None,
+ process_after: Annotated[Union[timedelta, None], Body()] = None,
+):
+ start_process = start_datetime + process_after
+ duration = end_datetime - start_process
+ return {
+ "item_id": item_id,
+ "start_datetime": start_datetime,
+ "end_datetime": end_datetime,
+ "repeat_at": repeat_at,
+ "process_after": process_after,
+ "start_process": start_process,
+ "duration": duration,
+ }
diff --git a/docs_src/extra_data_types/tutorial001_an_py310.py b/docs_src/extra_data_types/tutorial001_an_py310.py
new file mode 100644
index 000000000..4f69c40d9
--- /dev/null
+++ b/docs_src/extra_data_types/tutorial001_an_py310.py
@@ -0,0 +1,28 @@
+from datetime import datetime, time, timedelta
+from typing import Annotated
+from uuid import UUID
+
+from fastapi import Body, FastAPI
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def read_items(
+ item_id: UUID,
+ start_datetime: Annotated[datetime | None, Body()] = None,
+ end_datetime: Annotated[datetime | None, Body()] = None,
+ repeat_at: Annotated[time | None, Body()] = None,
+ process_after: Annotated[timedelta | None, Body()] = None,
+):
+ start_process = start_datetime + process_after
+ duration = end_datetime - start_process
+ return {
+ "item_id": item_id,
+ "start_datetime": start_datetime,
+ "end_datetime": end_datetime,
+ "repeat_at": repeat_at,
+ "process_after": process_after,
+ "start_process": start_process,
+ "duration": duration,
+ }
diff --git a/docs_src/extra_data_types/tutorial001_an_py39.py b/docs_src/extra_data_types/tutorial001_an_py39.py
new file mode 100644
index 000000000..630d36ae3
--- /dev/null
+++ b/docs_src/extra_data_types/tutorial001_an_py39.py
@@ -0,0 +1,28 @@
+from datetime import datetime, time, timedelta
+from typing import Annotated, Union
+from uuid import UUID
+
+from fastapi import Body, FastAPI
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def read_items(
+ item_id: UUID,
+ start_datetime: Annotated[Union[datetime, None], Body()] = None,
+ end_datetime: Annotated[Union[datetime, None], Body()] = None,
+ repeat_at: Annotated[Union[time, None], Body()] = None,
+ process_after: Annotated[Union[timedelta, None], Body()] = None,
+):
+ start_process = start_datetime + process_after
+ duration = end_datetime - start_process
+ return {
+ "item_id": item_id,
+ "start_datetime": start_datetime,
+ "end_datetime": end_datetime,
+ "repeat_at": repeat_at,
+ "process_after": process_after,
+ "start_process": start_process,
+ "duration": duration,
+ }
diff --git a/docs_src/header_params/tutorial001_an.py b/docs_src/header_params/tutorial001_an.py
new file mode 100644
index 000000000..816c00086
--- /dev/null
+++ b/docs_src/header_params/tutorial001_an.py
@@ -0,0 +1,11 @@
+from typing import Union
+
+from fastapi import FastAPI, Header
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(user_agent: Annotated[Union[str, None], Header()] = None):
+ return {"User-Agent": user_agent}
diff --git a/docs_src/header_params/tutorial001_an_py310.py b/docs_src/header_params/tutorial001_an_py310.py
new file mode 100644
index 000000000..4ba5e1bfb
--- /dev/null
+++ b/docs_src/header_params/tutorial001_an_py310.py
@@ -0,0 +1,10 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(user_agent: Annotated[str | None, Header()] = None):
+ return {"User-Agent": user_agent}
diff --git a/docs_src/header_params/tutorial001_an_py39.py b/docs_src/header_params/tutorial001_an_py39.py
new file mode 100644
index 000000000..1fbe3bb99
--- /dev/null
+++ b/docs_src/header_params/tutorial001_an_py39.py
@@ -0,0 +1,10 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(user_agent: Annotated[Union[str, None], Header()] = None):
+ return {"User-Agent": user_agent}
diff --git a/docs_src/header_params/tutorial002_an.py b/docs_src/header_params/tutorial002_an.py
new file mode 100644
index 000000000..65d972d46
--- /dev/null
+++ b/docs_src/header_params/tutorial002_an.py
@@ -0,0 +1,15 @@
+from typing import Union
+
+from fastapi import FastAPI, Header
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ strange_header: Annotated[
+ Union[str, None], Header(convert_underscores=False)
+ ] = None
+):
+ return {"strange_header": strange_header}
diff --git a/docs_src/header_params/tutorial002_an_py310.py b/docs_src/header_params/tutorial002_an_py310.py
new file mode 100644
index 000000000..b340647b6
--- /dev/null
+++ b/docs_src/header_params/tutorial002_an_py310.py
@@ -0,0 +1,12 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ strange_header: Annotated[str | None, Header(convert_underscores=False)] = None
+):
+ return {"strange_header": strange_header}
diff --git a/docs_src/header_params/tutorial002_an_py39.py b/docs_src/header_params/tutorial002_an_py39.py
new file mode 100644
index 000000000..7f6a99f9c
--- /dev/null
+++ b/docs_src/header_params/tutorial002_an_py39.py
@@ -0,0 +1,14 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ strange_header: Annotated[
+ Union[str, None], Header(convert_underscores=False)
+ ] = None
+):
+ return {"strange_header": strange_header}
diff --git a/docs_src/header_params/tutorial003_an.py b/docs_src/header_params/tutorial003_an.py
new file mode 100644
index 000000000..5406fd1f8
--- /dev/null
+++ b/docs_src/header_params/tutorial003_an.py
@@ -0,0 +1,11 @@
+from typing import List, Union
+
+from fastapi import FastAPI, Header
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(x_token: Annotated[Union[List[str], None], Header()] = None):
+ return {"X-Token values": x_token}
diff --git a/docs_src/header_params/tutorial003_an_py310.py b/docs_src/header_params/tutorial003_an_py310.py
new file mode 100644
index 000000000..0e78cf029
--- /dev/null
+++ b/docs_src/header_params/tutorial003_an_py310.py
@@ -0,0 +1,10 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(x_token: Annotated[list[str] | None, Header()] = None):
+ return {"X-Token values": x_token}
diff --git a/docs_src/header_params/tutorial003_an_py39.py b/docs_src/header_params/tutorial003_an_py39.py
new file mode 100644
index 000000000..c1dd49961
--- /dev/null
+++ b/docs_src/header_params/tutorial003_an_py39.py
@@ -0,0 +1,10 @@
+from typing import Annotated, List, Union
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(x_token: Annotated[Union[List[str], None], Header()] = None):
+ return {"X-Token values": x_token}
diff --git a/docs_src/path_params_numeric_validations/tutorial001_an.py b/docs_src/path_params_numeric_validations/tutorial001_an.py
new file mode 100644
index 000000000..621be7b04
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial001_an.py
@@ -0,0 +1,17 @@
+from typing import Union
+
+from fastapi import FastAPI, Path, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get")],
+ q: Annotated[Union[str, None], Query(alias="item-query")] = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial001_an_py310.py b/docs_src/path_params_numeric_validations/tutorial001_an_py310.py
new file mode 100644
index 000000000..c4db263f0
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial001_an_py310.py
@@ -0,0 +1,16 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Path, Query
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get")],
+ q: Annotated[str | None, Query(alias="item-query")] = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial001_an_py39.py b/docs_src/path_params_numeric_validations/tutorial001_an_py39.py
new file mode 100644
index 000000000..b36315a46
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial001_an_py39.py
@@ -0,0 +1,16 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Path, Query
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get")],
+ q: Annotated[Union[str, None], Query(alias="item-query")] = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial002_an.py b/docs_src/path_params_numeric_validations/tutorial002_an.py
new file mode 100644
index 000000000..322f8cf0b
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial002_an.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, Path
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ q: str, item_id: Annotated[int, Path(title="The ID of the item to get")]
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial002_an_py39.py b/docs_src/path_params_numeric_validations/tutorial002_an_py39.py
new file mode 100644
index 000000000..cd882abb2
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial002_an_py39.py
@@ -0,0 +1,15 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Path
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ q: str, item_id: Annotated[int, Path(title="The ID of the item to get")]
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial003_an.py b/docs_src/path_params_numeric_validations/tutorial003_an.py
new file mode 100644
index 000000000..d0fa8b3db
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial003_an.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, Path
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get")], q: str
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial003_an_py39.py b/docs_src/path_params_numeric_validations/tutorial003_an_py39.py
new file mode 100644
index 000000000..1588556e7
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial003_an_py39.py
@@ -0,0 +1,15 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Path
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get")], q: str
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial004_an.py b/docs_src/path_params_numeric_validations/tutorial004_an.py
new file mode 100644
index 000000000..ffc50f6c5
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial004_an.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, Path
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial004_an_py39.py b/docs_src/path_params_numeric_validations/tutorial004_an_py39.py
new file mode 100644
index 000000000..f67f6450e
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial004_an_py39.py
@@ -0,0 +1,15 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Path
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial005_an.py b/docs_src/path_params_numeric_validations/tutorial005_an.py
new file mode 100644
index 000000000..433c69129
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial005_an.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI, Path
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)],
+ q: str,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial005_an_py39.py b/docs_src/path_params_numeric_validations/tutorial005_an_py39.py
new file mode 100644
index 000000000..571dd583c
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial005_an_py39.py
@@ -0,0 +1,16 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Path
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)],
+ q: str,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial006.py b/docs_src/path_params_numeric_validations/tutorial006.py
index 85bd6e8b4..0ea32694a 100644
--- a/docs_src/path_params_numeric_validations/tutorial006.py
+++ b/docs_src/path_params_numeric_validations/tutorial006.py
@@ -8,7 +8,7 @@ async def read_items(
*,
item_id: int = Path(title="The ID of the item to get", ge=0, le=1000),
q: str,
- size: float = Query(gt=0, lt=10.5)
+ size: float = Query(gt=0, lt=10.5),
):
results = {"item_id": item_id}
if q:
diff --git a/docs_src/path_params_numeric_validations/tutorial006_an.py b/docs_src/path_params_numeric_validations/tutorial006_an.py
new file mode 100644
index 000000000..22a143623
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial006_an.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI, Path, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ *,
+ item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
+ q: str,
+ size: Annotated[float, Query(gt=0, lt=10.5)],
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/path_params_numeric_validations/tutorial006_an_py39.py b/docs_src/path_params_numeric_validations/tutorial006_an_py39.py
new file mode 100644
index 000000000..804751893
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial006_an_py39.py
@@ -0,0 +1,18 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Path, Query
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ *,
+ item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
+ q: str,
+ size: Annotated[float, Query(gt=0, lt=10.5)],
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/python_types/tutorial013.py b/docs_src/python_types/tutorial013.py
new file mode 100644
index 000000000..0ec773519
--- /dev/null
+++ b/docs_src/python_types/tutorial013.py
@@ -0,0 +1,5 @@
+from typing_extensions import Annotated
+
+
+def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
+ return f"Hello {name}"
diff --git a/docs_src/python_types/tutorial013_py39.py b/docs_src/python_types/tutorial013_py39.py
new file mode 100644
index 000000000..65a0eaa93
--- /dev/null
+++ b/docs_src/python_types/tutorial013_py39.py
@@ -0,0 +1,5 @@
+from typing import Annotated
+
+
+def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
+ return f"Hello {name}"
diff --git a/docs_src/query_params_str_validations/tutorial002_an.py b/docs_src/query_params_str_validations/tutorial002_an.py
new file mode 100644
index 000000000..cb1b38940
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial002_an.py
@@ -0,0 +1,14 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial002_an_py310.py b/docs_src/query_params_str_validations/tutorial002_an_py310.py
new file mode 100644
index 000000000..66ee7451b
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial002_an_py310.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial003_an.py b/docs_src/query_params_str_validations/tutorial003_an.py
new file mode 100644
index 000000000..a3665f6a8
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial003_an.py
@@ -0,0 +1,16 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial003_an_py310.py b/docs_src/query_params_str_validations/tutorial003_an_py310.py
new file mode 100644
index 000000000..836af04de
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial003_an_py310.py
@@ -0,0 +1,15 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[str | None, Query(min_length=3, max_length=50)] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial003_an_py39.py b/docs_src/query_params_str_validations/tutorial003_an_py39.py
new file mode 100644
index 000000000..87a426839
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial003_an_py39.py
@@ -0,0 +1,15 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial004_an.py b/docs_src/query_params_str_validations/tutorial004_an.py
new file mode 100644
index 000000000..5346b997b
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial004_an.py
@@ -0,0 +1,18 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ Union[str, None], Query(min_length=3, max_length=50, regex="^fixedquery$")
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial004_an_py310.py b/docs_src/query_params_str_validations/tutorial004_an_py310.py
new file mode 100644
index 000000000..8fd375b3d
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial004_an_py310.py
@@ -0,0 +1,17 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ str | None, Query(min_length=3, max_length=50, regex="^fixedquery$")
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial004_an_py39.py b/docs_src/query_params_str_validations/tutorial004_an_py39.py
new file mode 100644
index 000000000..2fd82db75
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial004_an_py39.py
@@ -0,0 +1,17 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ Union[str, None], Query(min_length=3, max_length=50, regex="^fixedquery$")
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial005_an.py b/docs_src/query_params_str_validations/tutorial005_an.py
new file mode 100644
index 000000000..452d4d38d
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial005_an.py
@@ -0,0 +1,12 @@
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial005_an_py39.py b/docs_src/query_params_str_validations/tutorial005_an_py39.py
new file mode 100644
index 000000000..b1f6046b5
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial005_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006_an.py b/docs_src/query_params_str_validations/tutorial006_an.py
new file mode 100644
index 000000000..559480d2b
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006_an.py
@@ -0,0 +1,12 @@
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)]):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006_an_py39.py b/docs_src/query_params_str_validations/tutorial006_an_py39.py
new file mode 100644
index 000000000..3b4a676d2
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)]):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006b_an.py b/docs_src/query_params_str_validations/tutorial006b_an.py
new file mode 100644
index 000000000..ea3b02583
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006b_an.py
@@ -0,0 +1,12 @@
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006b_an_py39.py b/docs_src/query_params_str_validations/tutorial006b_an_py39.py
new file mode 100644
index 000000000..687a9f544
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006b_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py
new file mode 100644
index 000000000..10bf26a57
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006c_an.py
@@ -0,0 +1,14 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py310.py b/docs_src/query_params_str_validations/tutorial006c_an_py310.py
new file mode 100644
index 000000000..1ab0a7d53
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006c_an_py310.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str | None, Query(min_length=3)] = ...):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py39.py b/docs_src/query_params_str_validations/tutorial006c_an_py39.py
new file mode 100644
index 000000000..ac1273331
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006c_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006d_an.py b/docs_src/query_params_str_validations/tutorial006d_an.py
new file mode 100644
index 000000000..bc8283e15
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006d_an.py
@@ -0,0 +1,13 @@
+from fastapi import FastAPI, Query
+from pydantic import Required
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)] = Required):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial006d_an_py39.py b/docs_src/query_params_str_validations/tutorial006d_an_py39.py
new file mode 100644
index 000000000..035d9e3bd
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial006d_an_py39.py
@@ -0,0 +1,14 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+from pydantic import Required
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str, Query(min_length=3)] = Required):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial007_an.py b/docs_src/query_params_str_validations/tutorial007_an.py
new file mode 100644
index 000000000..3bc85cc0c
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial007_an.py
@@ -0,0 +1,16 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial007_an_py310.py b/docs_src/query_params_str_validations/tutorial007_an_py310.py
new file mode 100644
index 000000000..5933911fd
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial007_an_py310.py
@@ -0,0 +1,15 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[str | None, Query(title="Query string", min_length=3)] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial007_an_py39.py b/docs_src/query_params_str_validations/tutorial007_an_py39.py
new file mode 100644
index 000000000..dafa1c5c9
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial007_an_py39.py
@@ -0,0 +1,15 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial008_an.py b/docs_src/query_params_str_validations/tutorial008_an.py
new file mode 100644
index 000000000..5699f1e88
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial008_an.py
@@ -0,0 +1,23 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ Union[str, None],
+ Query(
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ ),
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial008_an_py310.py b/docs_src/query_params_str_validations/tutorial008_an_py310.py
new file mode 100644
index 000000000..4aaadf8b4
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial008_an_py310.py
@@ -0,0 +1,22 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ str | None,
+ Query(
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ ),
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial008_an_py39.py b/docs_src/query_params_str_validations/tutorial008_an_py39.py
new file mode 100644
index 000000000..1c3b36176
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial008_an_py39.py
@@ -0,0 +1,22 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ Union[str, None],
+ Query(
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ ),
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial009_an.py b/docs_src/query_params_str_validations/tutorial009_an.py
new file mode 100644
index 000000000..2894e2d51
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial009_an.py
@@ -0,0 +1,14 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial009_an_py310.py b/docs_src/query_params_str_validations/tutorial009_an_py310.py
new file mode 100644
index 000000000..d11753362
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial009_an_py310.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[str | None, Query(alias="item-query")] = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial009_an_py39.py b/docs_src/query_params_str_validations/tutorial009_an_py39.py
new file mode 100644
index 000000000..70a89e613
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial009_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial010_an.py b/docs_src/query_params_str_validations/tutorial010_an.py
new file mode 100644
index 000000000..8995f3f57
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial010_an.py
@@ -0,0 +1,27 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ Union[str, None],
+ Query(
+ alias="item-query",
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ max_length=50,
+ regex="^fixedquery$",
+ deprecated=True,
+ ),
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial010_an_py310.py b/docs_src/query_params_str_validations/tutorial010_an_py310.py
new file mode 100644
index 000000000..cfa81926c
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial010_an_py310.py
@@ -0,0 +1,26 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ str | None,
+ Query(
+ alias="item-query",
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ max_length=50,
+ regex="^fixedquery$",
+ deprecated=True,
+ ),
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial010_an_py39.py b/docs_src/query_params_str_validations/tutorial010_an_py39.py
new file mode 100644
index 000000000..220eaabf4
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial010_an_py39.py
@@ -0,0 +1,26 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: Annotated[
+ Union[str, None],
+ Query(
+ alias="item-query",
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ max_length=50,
+ regex="^fixedquery$",
+ deprecated=True,
+ ),
+ ] = None
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial011_an.py b/docs_src/query_params_str_validations/tutorial011_an.py
new file mode 100644
index 000000000..8ed699337
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial011_an.py
@@ -0,0 +1,12 @@
+from typing import List, Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[List[str], None], Query()] = None):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial011_an_py310.py b/docs_src/query_params_str_validations/tutorial011_an_py310.py
new file mode 100644
index 000000000..5dad4bfde
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial011_an_py310.py
@@ -0,0 +1,11 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[list[str] | None, Query()] = None):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial011_an_py39.py b/docs_src/query_params_str_validations/tutorial011_an_py39.py
new file mode 100644
index 000000000..416e3990d
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial011_an_py39.py
@@ -0,0 +1,11 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[Union[list[str], None], Query()] = None):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial012_an.py b/docs_src/query_params_str_validations/tutorial012_an.py
new file mode 100644
index 000000000..261af250a
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial012_an.py
@@ -0,0 +1,12 @@
+from typing import List
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[List[str], Query()] = ["foo", "bar"]):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial012_an_py39.py b/docs_src/query_params_str_validations/tutorial012_an_py39.py
new file mode 100644
index 000000000..9b5a9c2fb
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial012_an_py39.py
@@ -0,0 +1,11 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial013_an.py b/docs_src/query_params_str_validations/tutorial013_an.py
new file mode 100644
index 000000000..f12a25055
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial013_an.py
@@ -0,0 +1,10 @@
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[list, Query()] = []):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial013_an_py39.py b/docs_src/query_params_str_validations/tutorial013_an_py39.py
new file mode 100644
index 000000000..602734145
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial013_an_py39.py
@@ -0,0 +1,11 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Annotated[list, Query()] = []):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial014_an.py b/docs_src/query_params_str_validations/tutorial014_an.py
new file mode 100644
index 000000000..a9a9c4427
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial014_an.py
@@ -0,0 +1,16 @@
+from typing import Union
+
+from fastapi import FastAPI, Query
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None
+):
+ if hidden_query:
+ return {"hidden_query": hidden_query}
+ else:
+ return {"hidden_query": "Not found"}
diff --git a/docs_src/query_params_str_validations/tutorial014_an_py310.py b/docs_src/query_params_str_validations/tutorial014_an_py310.py
new file mode 100644
index 000000000..5fba54150
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial014_an_py310.py
@@ -0,0 +1,15 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None
+):
+ if hidden_query:
+ return {"hidden_query": hidden_query}
+ else:
+ return {"hidden_query": "Not found"}
diff --git a/docs_src/query_params_str_validations/tutorial014_an_py39.py b/docs_src/query_params_str_validations/tutorial014_an_py39.py
new file mode 100644
index 000000000..b07985210
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial014_an_py39.py
@@ -0,0 +1,15 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None
+):
+ if hidden_query:
+ return {"hidden_query": hidden_query}
+ else:
+ return {"hidden_query": "Not found"}
diff --git a/docs_src/request_files/tutorial001_02_an.py b/docs_src/request_files/tutorial001_02_an.py
new file mode 100644
index 000000000..5007fef15
--- /dev/null
+++ b/docs_src/request_files/tutorial001_02_an.py
@@ -0,0 +1,22 @@
+from typing import Union
+
+from fastapi import FastAPI, File, UploadFile
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Annotated[Union[bytes, None], File()] = None):
+ if not file:
+ return {"message": "No file sent"}
+ else:
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: Union[UploadFile, None] = None):
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_02_an_py310.py b/docs_src/request_files/tutorial001_02_an_py310.py
new file mode 100644
index 000000000..9b80321b4
--- /dev/null
+++ b/docs_src/request_files/tutorial001_02_an_py310.py
@@ -0,0 +1,21 @@
+from typing import Annotated
+
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Annotated[bytes | None, File()] = None):
+ if not file:
+ return {"message": "No file sent"}
+ else:
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: UploadFile | None = None):
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_02_an_py39.py b/docs_src/request_files/tutorial001_02_an_py39.py
new file mode 100644
index 000000000..bb090ff6c
--- /dev/null
+++ b/docs_src/request_files/tutorial001_02_an_py39.py
@@ -0,0 +1,21 @@
+from typing import Annotated, Union
+
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Annotated[Union[bytes, None], File()] = None):
+ if not file:
+ return {"message": "No file sent"}
+ else:
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: Union[UploadFile, None] = None):
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_03_an.py b/docs_src/request_files/tutorial001_03_an.py
new file mode 100644
index 000000000..8a6b0a245
--- /dev/null
+++ b/docs_src/request_files/tutorial001_03_an.py
@@ -0,0 +1,16 @@
+from fastapi import FastAPI, File, UploadFile
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(
+ file: Annotated[UploadFile, File(description="A file read as UploadFile")],
+):
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_03_an_py39.py b/docs_src/request_files/tutorial001_03_an_py39.py
new file mode 100644
index 000000000..93098a677
--- /dev/null
+++ b/docs_src/request_files/tutorial001_03_an_py39.py
@@ -0,0 +1,17 @@
+from typing import Annotated
+
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(
+ file: Annotated[UploadFile, File(description="A file read as UploadFile")],
+):
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_an.py b/docs_src/request_files/tutorial001_an.py
new file mode 100644
index 000000000..ca2f76d5c
--- /dev/null
+++ b/docs_src/request_files/tutorial001_an.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, File, UploadFile
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Annotated[bytes, File()]):
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: UploadFile):
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_an_py39.py b/docs_src/request_files/tutorial001_an_py39.py
new file mode 100644
index 000000000..26a767221
--- /dev/null
+++ b/docs_src/request_files/tutorial001_an_py39.py
@@ -0,0 +1,15 @@
+from typing import Annotated
+
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Annotated[bytes, File()]):
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: UploadFile):
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial002_an.py b/docs_src/request_files/tutorial002_an.py
new file mode 100644
index 000000000..eaa90da2b
--- /dev/null
+++ b/docs_src/request_files/tutorial002_an.py
@@ -0,0 +1,34 @@
+from typing import List
+
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(files: Annotated[List[bytes], File()]):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(files: List[UploadFile]):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial002_an_py39.py b/docs_src/request_files/tutorial002_an_py39.py
new file mode 100644
index 000000000..db524ceab
--- /dev/null
+++ b/docs_src/request_files/tutorial002_an_py39.py
@@ -0,0 +1,33 @@
+from typing import Annotated
+
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(files: Annotated[list[bytes], File()]):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(files: list[UploadFile]):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003_an.py b/docs_src/request_files/tutorial003_an.py
new file mode 100644
index 000000000..2238e3c94
--- /dev/null
+++ b/docs_src/request_files/tutorial003_an.py
@@ -0,0 +1,40 @@
+from typing import List
+
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(
+ files: Annotated[List[bytes], File(description="Multiple files as bytes")],
+):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(
+ files: Annotated[
+ List[UploadFile], File(description="Multiple files as UploadFile")
+ ],
+):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003_an_py39.py b/docs_src/request_files/tutorial003_an_py39.py
new file mode 100644
index 000000000..5a8c5dab5
--- /dev/null
+++ b/docs_src/request_files/tutorial003_an_py39.py
@@ -0,0 +1,39 @@
+from typing import Annotated
+
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(
+ files: Annotated[list[bytes], File(description="Multiple files as bytes")],
+):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(
+ files: Annotated[
+ list[UploadFile], File(description="Multiple files as UploadFile")
+ ],
+):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_forms/tutorial001_an.py b/docs_src/request_forms/tutorial001_an.py
new file mode 100644
index 000000000..677fbf2db
--- /dev/null
+++ b/docs_src/request_forms/tutorial001_an.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI, Form
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/login/")
+async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
+ return {"username": username}
diff --git a/docs_src/request_forms/tutorial001_an_py39.py b/docs_src/request_forms/tutorial001_an_py39.py
new file mode 100644
index 000000000..8e9d2ea53
--- /dev/null
+++ b/docs_src/request_forms/tutorial001_an_py39.py
@@ -0,0 +1,10 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Form
+
+app = FastAPI()
+
+
+@app.post("/login/")
+async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
+ return {"username": username}
diff --git a/docs_src/request_forms_and_files/tutorial001_an.py b/docs_src/request_forms_and_files/tutorial001_an.py
new file mode 100644
index 000000000..0ea285ac8
--- /dev/null
+++ b/docs_src/request_forms_and_files/tutorial001_an.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI, File, Form, UploadFile
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(
+ file: Annotated[bytes, File()],
+ fileb: Annotated[UploadFile, File()],
+ token: Annotated[str, Form()],
+):
+ return {
+ "file_size": len(file),
+ "token": token,
+ "fileb_content_type": fileb.content_type,
+ }
diff --git a/docs_src/request_forms_and_files/tutorial001_an_py39.py b/docs_src/request_forms_and_files/tutorial001_an_py39.py
new file mode 100644
index 000000000..12cc43e50
--- /dev/null
+++ b/docs_src/request_forms_and_files/tutorial001_an_py39.py
@@ -0,0 +1,18 @@
+from typing import Annotated
+
+from fastapi import FastAPI, File, Form, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(
+ file: Annotated[bytes, File()],
+ fileb: Annotated[UploadFile, File()],
+ token: Annotated[str, Form()],
+):
+ return {
+ "file_size": len(file),
+ "token": token,
+ "fileb_content_type": fileb.content_type,
+ }
diff --git a/docs_src/response_model/tutorial001.py b/docs_src/response_model/tutorial001.py
index 0f6e03e5b..fd1c902a5 100644
--- a/docs_src/response_model/tutorial001.py
+++ b/docs_src/response_model/tutorial001.py
@@ -1,4 +1,4 @@
-from typing import List, Union
+from typing import Any, List, Union
from fastapi import FastAPI
from pydantic import BaseModel
@@ -15,5 +15,13 @@ class Item(BaseModel):
@app.post("/items/", response_model=Item)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Any:
return item
+
+
+@app.get("/items/", response_model=List[Item])
+async def read_items() -> Any:
+ return [
+ {"name": "Portal Gun", "price": 42.0},
+ {"name": "Plumbus", "price": 32.0},
+ ]
diff --git a/docs_src/response_model/tutorial001_01.py b/docs_src/response_model/tutorial001_01.py
new file mode 100644
index 000000000..98d30d540
--- /dev/null
+++ b/docs_src/response_model/tutorial001_01.py
@@ -0,0 +1,27 @@
+from typing import List, Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+ tags: List[str] = []
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
+
+
+@app.get("/items/")
+async def read_items() -> List[Item]:
+ return [
+ Item(name="Portal Gun", price=42.0),
+ Item(name="Plumbus", price=32.0),
+ ]
diff --git a/docs_src/response_model/tutorial001_01_py310.py b/docs_src/response_model/tutorial001_01_py310.py
new file mode 100644
index 000000000..7951c1076
--- /dev/null
+++ b/docs_src/response_model/tutorial001_01_py310.py
@@ -0,0 +1,25 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list[str] = []
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
+
+
+@app.get("/items/")
+async def read_items() -> list[Item]:
+ return [
+ Item(name="Portal Gun", price=42.0),
+ Item(name="Plumbus", price=32.0),
+ ]
diff --git a/docs_src/response_model/tutorial001_01_py39.py b/docs_src/response_model/tutorial001_01_py39.py
new file mode 100644
index 000000000..16c78aa3f
--- /dev/null
+++ b/docs_src/response_model/tutorial001_01_py39.py
@@ -0,0 +1,27 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+ tags: list[str] = []
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
+
+
+@app.get("/items/")
+async def read_items() -> list[Item]:
+ return [
+ Item(name="Portal Gun", price=42.0),
+ Item(name="Plumbus", price=32.0),
+ ]
diff --git a/docs_src/response_model/tutorial001_py310.py b/docs_src/response_model/tutorial001_py310.py
index 59efecde4..f8a2aa9fc 100644
--- a/docs_src/response_model/tutorial001_py310.py
+++ b/docs_src/response_model/tutorial001_py310.py
@@ -1,3 +1,5 @@
+from typing import Any
+
from fastapi import FastAPI
from pydantic import BaseModel
@@ -13,5 +15,13 @@ class Item(BaseModel):
@app.post("/items/", response_model=Item)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Any:
return item
+
+
+@app.get("/items/", response_model=list[Item])
+async def read_items() -> Any:
+ return [
+ {"name": "Portal Gun", "price": 42.0},
+ {"name": "Plumbus", "price": 32.0},
+ ]
diff --git a/docs_src/response_model/tutorial001_py39.py b/docs_src/response_model/tutorial001_py39.py
index cdcca39d2..261e252d0 100644
--- a/docs_src/response_model/tutorial001_py39.py
+++ b/docs_src/response_model/tutorial001_py39.py
@@ -1,4 +1,4 @@
-from typing import Union
+from typing import Any, Union
from fastapi import FastAPI
from pydantic import BaseModel
@@ -15,5 +15,13 @@ class Item(BaseModel):
@app.post("/items/", response_model=Item)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Any:
return item
+
+
+@app.get("/items/", response_model=list[Item])
+async def read_items() -> Any:
+ return [
+ {"name": "Portal Gun", "price": 42.0},
+ {"name": "Plumbus", "price": 32.0},
+ ]
diff --git a/docs_src/response_model/tutorial002.py b/docs_src/response_model/tutorial002.py
index c68e8b138..a58668f9e 100644
--- a/docs_src/response_model/tutorial002.py
+++ b/docs_src/response_model/tutorial002.py
@@ -14,6 +14,6 @@ class UserIn(BaseModel):
# Don't do this in production!
-@app.post("/user/", response_model=UserIn)
-async def create_user(user: UserIn):
+@app.post("/user/")
+async def create_user(user: UserIn) -> UserIn:
return user
diff --git a/docs_src/response_model/tutorial002_py310.py b/docs_src/response_model/tutorial002_py310.py
index 29ab9c9d2..0a91a5967 100644
--- a/docs_src/response_model/tutorial002_py310.py
+++ b/docs_src/response_model/tutorial002_py310.py
@@ -12,6 +12,6 @@ class UserIn(BaseModel):
# Don't do this in production!
-@app.post("/user/", response_model=UserIn)
-async def create_user(user: UserIn):
+@app.post("/user/")
+async def create_user(user: UserIn) -> UserIn:
return user
diff --git a/docs_src/response_model/tutorial003.py b/docs_src/response_model/tutorial003.py
index 37e493dcb..c42dbc707 100644
--- a/docs_src/response_model/tutorial003.py
+++ b/docs_src/response_model/tutorial003.py
@@ -1,4 +1,4 @@
-from typing import Union
+from typing import Any, Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
@@ -20,5 +20,5 @@ class UserOut(BaseModel):
@app.post("/user/", response_model=UserOut)
-async def create_user(user: UserIn):
+async def create_user(user: UserIn) -> Any:
return user
diff --git a/docs_src/response_model/tutorial003_01.py b/docs_src/response_model/tutorial003_01.py
new file mode 100644
index 000000000..52694b551
--- /dev/null
+++ b/docs_src/response_model/tutorial003_01.py
@@ -0,0 +1,21 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class BaseUser(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: Union[str, None] = None
+
+
+class UserIn(BaseUser):
+ password: str
+
+
+@app.post("/user/")
+async def create_user(user: UserIn) -> BaseUser:
+ return user
diff --git a/docs_src/response_model/tutorial003_01_py310.py b/docs_src/response_model/tutorial003_01_py310.py
new file mode 100644
index 000000000..6ffddfd0a
--- /dev/null
+++ b/docs_src/response_model/tutorial003_01_py310.py
@@ -0,0 +1,19 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class BaseUser(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserIn(BaseUser):
+ password: str
+
+
+@app.post("/user/")
+async def create_user(user: UserIn) -> BaseUser:
+ return user
diff --git a/docs_src/response_model/tutorial003_02.py b/docs_src/response_model/tutorial003_02.py
new file mode 100644
index 000000000..df6a09646
--- /dev/null
+++ b/docs_src/response_model/tutorial003_02.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Response
+from fastapi.responses import JSONResponse, RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Response:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return JSONResponse(content={"message": "Here's your interdimensional portal."})
diff --git a/docs_src/response_model/tutorial003_03.py b/docs_src/response_model/tutorial003_03.py
new file mode 100644
index 000000000..0d4bd8de5
--- /dev/null
+++ b/docs_src/response_model/tutorial003_03.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/teleport")
+async def get_teleport() -> RedirectResponse:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
diff --git a/docs_src/response_model/tutorial003_04.py b/docs_src/response_model/tutorial003_04.py
new file mode 100644
index 000000000..b13a92692
--- /dev/null
+++ b/docs_src/response_model/tutorial003_04.py
@@ -0,0 +1,13 @@
+from typing import Union
+
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Union[Response, dict]:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_04_py310.py b/docs_src/response_model/tutorial003_04_py310.py
new file mode 100644
index 000000000..cee49b83e
--- /dev/null
+++ b/docs_src/response_model/tutorial003_04_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Response | dict:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_05.py b/docs_src/response_model/tutorial003_05.py
new file mode 100644
index 000000000..0962061a6
--- /dev/null
+++ b/docs_src/response_model/tutorial003_05.py
@@ -0,0 +1,13 @@
+from typing import Union
+
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal", response_model=None)
+async def get_portal(teleport: bool = False) -> Union[Response, dict]:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_05_py310.py b/docs_src/response_model/tutorial003_05_py310.py
new file mode 100644
index 000000000..f1c0f8e12
--- /dev/null
+++ b/docs_src/response_model/tutorial003_05_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal", response_model=None)
+async def get_portal(teleport: bool = False) -> Response | dict:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_py310.py b/docs_src/response_model/tutorial003_py310.py
index fc9693e3c..3703bf888 100644
--- a/docs_src/response_model/tutorial003_py310.py
+++ b/docs_src/response_model/tutorial003_py310.py
@@ -1,3 +1,5 @@
+from typing import Any
+
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
@@ -18,5 +20,5 @@ class UserOut(BaseModel):
@app.post("/user/", response_model=UserOut)
-async def create_user(user: UserIn):
+async def create_user(user: UserIn) -> Any:
return user
diff --git a/docs_src/schema_extra_example/tutorial003_an.py b/docs_src/schema_extra_example/tutorial003_an.py
new file mode 100644
index 000000000..1dec555a9
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial003_an.py
@@ -0,0 +1,33 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ example={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ ),
+ ],
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial003_an_py310.py b/docs_src/schema_extra_example/tutorial003_an_py310.py
new file mode 100644
index 000000000..9edaddfb8
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial003_an_py310.py
@@ -0,0 +1,32 @@
+from typing import Annotated
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ example={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ ),
+ ],
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial003_an_py39.py b/docs_src/schema_extra_example/tutorial003_an_py39.py
new file mode 100644
index 000000000..fe08847d9
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial003_an_py39.py
@@ -0,0 +1,32 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ example={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ ),
+ ],
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial004_an.py b/docs_src/schema_extra_example/tutorial004_an.py
new file mode 100644
index 000000000..82c9a92ac
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial004_an.py
@@ -0,0 +1,55 @@
+from typing import Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ examples={
+ "normal": {
+ "summary": "A normal example",
+ "description": "A **normal** item works correctly.",
+ "value": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ },
+ "converted": {
+ "summary": "An example with converted data",
+ "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
+ "value": {
+ "name": "Bar",
+ "price": "35.4",
+ },
+ },
+ "invalid": {
+ "summary": "Invalid data is rejected with an error",
+ "value": {
+ "name": "Baz",
+ "price": "thirty five point four",
+ },
+ },
+ },
+ ),
+ ],
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial004_an_py310.py b/docs_src/schema_extra_example/tutorial004_an_py310.py
new file mode 100644
index 000000000..01f1a486c
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial004_an_py310.py
@@ -0,0 +1,54 @@
+from typing import Annotated
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ examples={
+ "normal": {
+ "summary": "A normal example",
+ "description": "A **normal** item works correctly.",
+ "value": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ },
+ "converted": {
+ "summary": "An example with converted data",
+ "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
+ "value": {
+ "name": "Bar",
+ "price": "35.4",
+ },
+ },
+ "invalid": {
+ "summary": "Invalid data is rejected with an error",
+ "value": {
+ "name": "Baz",
+ "price": "thirty five point four",
+ },
+ },
+ },
+ ),
+ ],
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial004_an_py39.py b/docs_src/schema_extra_example/tutorial004_an_py39.py
new file mode 100644
index 000000000..d50e8aa5f
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial004_an_py39.py
@@ -0,0 +1,54 @@
+from typing import Annotated, Union
+
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Annotated[
+ Item,
+ Body(
+ examples={
+ "normal": {
+ "summary": "A normal example",
+ "description": "A **normal** item works correctly.",
+ "value": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ },
+ "converted": {
+ "summary": "An example with converted data",
+ "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
+ "value": {
+ "name": "Bar",
+ "price": "35.4",
+ },
+ },
+ "invalid": {
+ "summary": "Invalid data is rejected with an error",
+ "value": {
+ "name": "Baz",
+ "price": "thirty five point four",
+ },
+ },
+ },
+ ),
+ ],
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/security/tutorial001_an.py b/docs_src/security/tutorial001_an.py
new file mode 100644
index 000000000..dac915b7c
--- /dev/null
+++ b/docs_src/security/tutorial001_an.py
@@ -0,0 +1,12 @@
+from fastapi import Depends, FastAPI
+from fastapi.security import OAuth2PasswordBearer
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+@app.get("/items/")
+async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
+ return {"token": token}
diff --git a/docs_src/security/tutorial001_an_py39.py b/docs_src/security/tutorial001_an_py39.py
new file mode 100644
index 000000000..de110402e
--- /dev/null
+++ b/docs_src/security/tutorial001_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+from fastapi.security import OAuth2PasswordBearer
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+@app.get("/items/")
+async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
+ return {"token": token}
diff --git a/docs_src/security/tutorial002_an.py b/docs_src/security/tutorial002_an.py
new file mode 100644
index 000000000..291b3bf53
--- /dev/null
+++ b/docs_src/security/tutorial002_an.py
@@ -0,0 +1,33 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI
+from fastapi.security import OAuth2PasswordBearer
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+def fake_decode_token(token):
+ return User(
+ username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
+ )
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ user = fake_decode_token(token)
+ return user
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
+ return current_user
diff --git a/docs_src/security/tutorial002_an_py310.py b/docs_src/security/tutorial002_an_py310.py
new file mode 100644
index 000000000..c7b761e45
--- /dev/null
+++ b/docs_src/security/tutorial002_an_py310.py
@@ -0,0 +1,32 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+from fastapi.security import OAuth2PasswordBearer
+from pydantic import BaseModel
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+def fake_decode_token(token):
+ return User(
+ username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
+ )
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ user = fake_decode_token(token)
+ return user
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
+ return current_user
diff --git a/docs_src/security/tutorial002_an_py39.py b/docs_src/security/tutorial002_an_py39.py
new file mode 100644
index 000000000..7ff1c470b
--- /dev/null
+++ b/docs_src/security/tutorial002_an_py39.py
@@ -0,0 +1,32 @@
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI
+from fastapi.security import OAuth2PasswordBearer
+from pydantic import BaseModel
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+def fake_decode_token(token):
+ return User(
+ username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
+ )
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ user = fake_decode_token(token)
+ return user
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]):
+ return current_user
diff --git a/docs_src/security/tutorial003_an.py b/docs_src/security/tutorial003_an.py
new file mode 100644
index 000000000..261cb4857
--- /dev/null
+++ b/docs_src/security/tutorial003_an.py
@@ -0,0 +1,95 @@
+from typing import Union
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "fakehashedsecret",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Wonderson",
+ "email": "alice@example.com",
+ "hashed_password": "fakehashedsecret2",
+ "disabled": True,
+ },
+}
+
+app = FastAPI()
+
+
+def fake_hash_password(password: str):
+ return "fakehashed" + password
+
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def fake_decode_token(token):
+ # This doesn't provide any security at all
+ # Check the next version
+ user = get_user(fake_users_db, token)
+ return user
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ user = fake_decode_token(token)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid authentication credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Depends(get_current_user)]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token")
+async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
+ user_dict = fake_users_db.get(form_data.username)
+ if not user_dict:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ user = UserInDB(**user_dict)
+ hashed_password = fake_hash_password(form_data.password)
+ if not hashed_password == user.hashed_password:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+
+ return {"access_token": user.username, "token_type": "bearer"}
+
+
+@app.get("/users/me")
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
diff --git a/docs_src/security/tutorial003_an_py310.py b/docs_src/security/tutorial003_an_py310.py
new file mode 100644
index 000000000..a03f4f8bf
--- /dev/null
+++ b/docs_src/security/tutorial003_an_py310.py
@@ -0,0 +1,94 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from pydantic import BaseModel
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "fakehashedsecret",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Wonderson",
+ "email": "alice@example.com",
+ "hashed_password": "fakehashedsecret2",
+ "disabled": True,
+ },
+}
+
+app = FastAPI()
+
+
+def fake_hash_password(password: str):
+ return "fakehashed" + password
+
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def fake_decode_token(token):
+ # This doesn't provide any security at all
+ # Check the next version
+ user = get_user(fake_users_db, token)
+ return user
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ user = fake_decode_token(token)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid authentication credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Depends(get_current_user)]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token")
+async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
+ user_dict = fake_users_db.get(form_data.username)
+ if not user_dict:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ user = UserInDB(**user_dict)
+ hashed_password = fake_hash_password(form_data.password)
+ if not hashed_password == user.hashed_password:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+
+ return {"access_token": user.username, "token_type": "bearer"}
+
+
+@app.get("/users/me")
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
diff --git a/docs_src/security/tutorial003_an_py39.py b/docs_src/security/tutorial003_an_py39.py
new file mode 100644
index 000000000..308dbe798
--- /dev/null
+++ b/docs_src/security/tutorial003_an_py39.py
@@ -0,0 +1,94 @@
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from pydantic import BaseModel
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "fakehashedsecret",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Wonderson",
+ "email": "alice@example.com",
+ "hashed_password": "fakehashedsecret2",
+ "disabled": True,
+ },
+}
+
+app = FastAPI()
+
+
+def fake_hash_password(password: str):
+ return "fakehashed" + password
+
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def fake_decode_token(token):
+ # This doesn't provide any security at all
+ # Check the next version
+ user = get_user(fake_users_db, token)
+ return user
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ user = fake_decode_token(token)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid authentication credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Depends(get_current_user)]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token")
+async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
+ user_dict = fake_users_db.get(form_data.username)
+ if not user_dict:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ user = UserInDB(**user_dict)
+ hashed_password = fake_hash_password(form_data.password)
+ if not hashed_password == user.hashed_password:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+
+ return {"access_token": user.username, "token_type": "bearer"}
+
+
+@app.get("/users/me")
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py
new file mode 100644
index 000000000..ca350343d
--- /dev/null
+++ b/docs_src/security/tutorial004_an.py
@@ -0,0 +1,147 @@
+from datetime import datetime, timedelta
+from typing import Union
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ }
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: Union[str, None] = None
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_data = TokenData(username=username)
+ except JWTError:
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Depends(get_current_user)]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
+):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect username or password",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username}, expires_delta=access_token_expires
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py
new file mode 100644
index 000000000..8bf5f3b71
--- /dev/null
+++ b/docs_src/security/tutorial004_an_py310.py
@@ -0,0 +1,146 @@
+from datetime import datetime, timedelta
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ }
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: str | None = None
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: timedelta | None = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_data = TokenData(username=username)
+ except JWTError:
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Depends(get_current_user)]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
+):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect username or password",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username}, expires_delta=access_token_expires
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py
new file mode 100644
index 000000000..a634e23de
--- /dev/null
+++ b/docs_src/security/tutorial004_an_py39.py
@@ -0,0 +1,146 @@
+from datetime import datetime, timedelta
+from typing import Annotated, Union
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ }
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: Union[str, None] = None
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_data = TokenData(username=username)
+ except JWTError:
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Depends(get_current_user)]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
+):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect username or password",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username}, expires_delta=access_token_expires
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py
new file mode 100644
index 000000000..ec4fa1a07
--- /dev/null
+++ b/docs_src/security/tutorial005_an.py
@@ -0,0 +1,178 @@
+from datetime import datetime, timedelta
+from typing import List, Union
+
+from fastapi import Depends, FastAPI, HTTPException, Security, status
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+ SecurityScopes,
+)
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel, ValidationError
+from typing_extensions import Annotated
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Chains",
+ "email": "alicechains@example.com",
+ "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
+ "disabled": True,
+ },
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: Union[str, None] = None
+ scopes: List[str] = []
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="token",
+ scopes={"me": "Read information about the current user.", "items": "Read items."},
+)
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(
+ security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]
+):
+ if security_scopes.scopes:
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
+ else:
+ authenticate_value = "Bearer"
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_scopes = payload.get("scopes", [])
+ token_data = TokenData(scopes=token_scopes, username=username)
+ except (JWTError, ValidationError):
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ for scope in security_scopes.scopes:
+ if scope not in token_data.scopes:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not enough permissions",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Security(get_current_user, scopes=["me"])]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
+):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username, "scopes": form_data.scopes},
+ expires_delta=access_token_expires,
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])]
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
+
+
+@app.get("/status/")
+async def read_system_status(current_user: Annotated[User, Depends(get_current_user)]):
+ return {"status": "ok"}
diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py
new file mode 100644
index 000000000..45f3fc0bd
--- /dev/null
+++ b/docs_src/security/tutorial005_an_py310.py
@@ -0,0 +1,177 @@
+from datetime import datetime, timedelta
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, HTTPException, Security, status
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+ SecurityScopes,
+)
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel, ValidationError
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Chains",
+ "email": "alicechains@example.com",
+ "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
+ "disabled": True,
+ },
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: str | None = None
+ scopes: list[str] = []
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="token",
+ scopes={"me": "Read information about the current user.", "items": "Read items."},
+)
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: timedelta | None = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(
+ security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]
+):
+ if security_scopes.scopes:
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
+ else:
+ authenticate_value = "Bearer"
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_scopes = payload.get("scopes", [])
+ token_data = TokenData(scopes=token_scopes, username=username)
+ except (JWTError, ValidationError):
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ for scope in security_scopes.scopes:
+ if scope not in token_data.scopes:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not enough permissions",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Security(get_current_user, scopes=["me"])]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
+):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username, "scopes": form_data.scopes},
+ expires_delta=access_token_expires,
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])]
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
+
+
+@app.get("/status/")
+async def read_system_status(current_user: Annotated[User, Depends(get_current_user)]):
+ return {"status": "ok"}
diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py
new file mode 100644
index 000000000..ecb5ed516
--- /dev/null
+++ b/docs_src/security/tutorial005_an_py39.py
@@ -0,0 +1,177 @@
+from datetime import datetime, timedelta
+from typing import Annotated, List, Union
+
+from fastapi import Depends, FastAPI, HTTPException, Security, status
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+ SecurityScopes,
+)
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel, ValidationError
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Chains",
+ "email": "alicechains@example.com",
+ "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
+ "disabled": True,
+ },
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: Union[str, None] = None
+ scopes: List[str] = []
+
+
+class User(BaseModel):
+ username: str
+ email: Union[str, None] = None
+ full_name: Union[str, None] = None
+ disabled: Union[bool, None] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="token",
+ scopes={"me": "Read information about the current user.", "items": "Read items."},
+)
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(
+ security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]
+):
+ if security_scopes.scopes:
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
+ else:
+ authenticate_value = "Bearer"
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_scopes = payload.get("scopes", [])
+ token_data = TokenData(scopes=token_scopes, username=username)
+ except (JWTError, ValidationError):
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ for scope in security_scopes.scopes:
+ if scope not in token_data.scopes:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not enough permissions",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: Annotated[User, Security(get_current_user, scopes=["me"])]
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
+):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username, "scopes": form_data.scopes},
+ expires_delta=access_token_expires,
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(
+ current_user: Annotated[User, Depends(get_current_active_user)]
+):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])]
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
+
+
+@app.get("/status/")
+async def read_system_status(current_user: Annotated[User, Depends(get_current_user)]):
+ return {"status": "ok"}
diff --git a/docs_src/security/tutorial006_an.py b/docs_src/security/tutorial006_an.py
new file mode 100644
index 000000000..985e4b2ad
--- /dev/null
+++ b/docs_src/security/tutorial006_an.py
@@ -0,0 +1,12 @@
+from fastapi import Depends, FastAPI
+from fastapi.security import HTTPBasic, HTTPBasicCredentials
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+security = HTTPBasic()
+
+
+@app.get("/users/me")
+def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
+ return {"username": credentials.username, "password": credentials.password}
diff --git a/docs_src/security/tutorial006_an_py39.py b/docs_src/security/tutorial006_an_py39.py
new file mode 100644
index 000000000..03c696a4b
--- /dev/null
+++ b/docs_src/security/tutorial006_an_py39.py
@@ -0,0 +1,13 @@
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+from fastapi.security import HTTPBasic, HTTPBasicCredentials
+
+app = FastAPI()
+
+security = HTTPBasic()
+
+
+@app.get("/users/me")
+def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
+ return {"username": credentials.username, "password": credentials.password}
diff --git a/docs_src/security/tutorial007_an.py b/docs_src/security/tutorial007_an.py
new file mode 100644
index 000000000..5fb7c8e57
--- /dev/null
+++ b/docs_src/security/tutorial007_an.py
@@ -0,0 +1,36 @@
+import secrets
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import HTTPBasic, HTTPBasicCredentials
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+security = HTTPBasic()
+
+
+def get_current_username(
+ credentials: Annotated[HTTPBasicCredentials, Depends(security)]
+):
+ current_username_bytes = credentials.username.encode("utf8")
+ correct_username_bytes = b"stanleyjobson"
+ is_correct_username = secrets.compare_digest(
+ current_username_bytes, correct_username_bytes
+ )
+ current_password_bytes = credentials.password.encode("utf8")
+ correct_password_bytes = b"swordfish"
+ is_correct_password = secrets.compare_digest(
+ current_password_bytes, correct_password_bytes
+ )
+ if not (is_correct_username and is_correct_password):
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect email or password",
+ headers={"WWW-Authenticate": "Basic"},
+ )
+ return credentials.username
+
+
+@app.get("/users/me")
+def read_current_user(username: Annotated[str, Depends(get_current_username)]):
+ return {"username": username}
diff --git a/docs_src/security/tutorial007_an_py39.py b/docs_src/security/tutorial007_an_py39.py
new file mode 100644
index 000000000..17177dabf
--- /dev/null
+++ b/docs_src/security/tutorial007_an_py39.py
@@ -0,0 +1,36 @@
+import secrets
+from typing import Annotated
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import HTTPBasic, HTTPBasicCredentials
+
+app = FastAPI()
+
+security = HTTPBasic()
+
+
+def get_current_username(
+ credentials: Annotated[HTTPBasicCredentials, Depends(security)]
+):
+ current_username_bytes = credentials.username.encode("utf8")
+ correct_username_bytes = b"stanleyjobson"
+ is_correct_username = secrets.compare_digest(
+ current_username_bytes, correct_username_bytes
+ )
+ current_password_bytes = credentials.password.encode("utf8")
+ correct_password_bytes = b"swordfish"
+ is_correct_password = secrets.compare_digest(
+ current_password_bytes, correct_password_bytes
+ )
+ if not (is_correct_username and is_correct_password):
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect email or password",
+ headers={"WWW-Authenticate": "Basic"},
+ )
+ return credentials.username
+
+
+@app.get("/users/me")
+def read_current_user(username: Annotated[str, Depends(get_current_username)]):
+ return {"username": username}
diff --git a/docs_src/settings/app02_an/__init__.py b/docs_src/settings/app02_an/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app02_an/config.py b/docs_src/settings/app02_an/config.py
new file mode 100644
index 000000000..9a7829135
--- /dev/null
+++ b/docs_src/settings/app02_an/config.py
@@ -0,0 +1,7 @@
+from pydantic import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "Awesome API"
+ admin_email: str
+ items_per_user: int = 50
diff --git a/docs_src/settings/app02_an/main.py b/docs_src/settings/app02_an/main.py
new file mode 100644
index 000000000..cb679202d
--- /dev/null
+++ b/docs_src/settings/app02_an/main.py
@@ -0,0 +1,22 @@
+from functools import lru_cache
+
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+from .config import Settings
+
+app = FastAPI()
+
+
+@lru_cache()
+def get_settings():
+ return Settings()
+
+
+@app.get("/info")
+async def info(settings: Annotated[Settings, Depends(get_settings)]):
+ return {
+ "app_name": settings.app_name,
+ "admin_email": settings.admin_email,
+ "items_per_user": settings.items_per_user,
+ }
diff --git a/docs_src/settings/app02_an/test_main.py b/docs_src/settings/app02_an/test_main.py
new file mode 100644
index 000000000..7a04d7e8e
--- /dev/null
+++ b/docs_src/settings/app02_an/test_main.py
@@ -0,0 +1,23 @@
+from fastapi.testclient import TestClient
+
+from .config import Settings
+from .main import app, get_settings
+
+client = TestClient(app)
+
+
+def get_settings_override():
+ return Settings(admin_email="testing_admin@example.com")
+
+
+app.dependency_overrides[get_settings] = get_settings_override
+
+
+def test_app():
+ response = client.get("/info")
+ data = response.json()
+ assert data == {
+ "app_name": "Awesome API",
+ "admin_email": "testing_admin@example.com",
+ "items_per_user": 50,
+ }
diff --git a/docs_src/settings/app02_an_py39/__init__.py b/docs_src/settings/app02_an_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app02_an_py39/config.py b/docs_src/settings/app02_an_py39/config.py
new file mode 100644
index 000000000..9a7829135
--- /dev/null
+++ b/docs_src/settings/app02_an_py39/config.py
@@ -0,0 +1,7 @@
+from pydantic import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "Awesome API"
+ admin_email: str
+ items_per_user: int = 50
diff --git a/docs_src/settings/app02_an_py39/main.py b/docs_src/settings/app02_an_py39/main.py
new file mode 100644
index 000000000..61be74fcb
--- /dev/null
+++ b/docs_src/settings/app02_an_py39/main.py
@@ -0,0 +1,22 @@
+from functools import lru_cache
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+from .config import Settings
+
+app = FastAPI()
+
+
+@lru_cache()
+def get_settings():
+ return Settings()
+
+
+@app.get("/info")
+async def info(settings: Annotated[Settings, Depends(get_settings)]):
+ return {
+ "app_name": settings.app_name,
+ "admin_email": settings.admin_email,
+ "items_per_user": settings.items_per_user,
+ }
diff --git a/docs_src/settings/app02_an_py39/test_main.py b/docs_src/settings/app02_an_py39/test_main.py
new file mode 100644
index 000000000..7a04d7e8e
--- /dev/null
+++ b/docs_src/settings/app02_an_py39/test_main.py
@@ -0,0 +1,23 @@
+from fastapi.testclient import TestClient
+
+from .config import Settings
+from .main import app, get_settings
+
+client = TestClient(app)
+
+
+def get_settings_override():
+ return Settings(admin_email="testing_admin@example.com")
+
+
+app.dependency_overrides[get_settings] = get_settings_override
+
+
+def test_app():
+ response = client.get("/info")
+ data = response.json()
+ assert data == {
+ "app_name": "Awesome API",
+ "admin_email": "testing_admin@example.com",
+ "items_per_user": 50,
+ }
diff --git a/docs_src/settings/app03_an/__init__.py b/docs_src/settings/app03_an/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app03_an/config.py b/docs_src/settings/app03_an/config.py
new file mode 100644
index 000000000..e1c3ee300
--- /dev/null
+++ b/docs_src/settings/app03_an/config.py
@@ -0,0 +1,10 @@
+from pydantic import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "Awesome API"
+ admin_email: str
+ items_per_user: int = 50
+
+ class Config:
+ env_file = ".env"
diff --git a/docs_src/settings/app03_an/main.py b/docs_src/settings/app03_an/main.py
new file mode 100644
index 000000000..c33b98f47
--- /dev/null
+++ b/docs_src/settings/app03_an/main.py
@@ -0,0 +1,22 @@
+from functools import lru_cache
+from typing import Annotated
+
+from fastapi import Depends, FastAPI
+
+from . import config
+
+app = FastAPI()
+
+
+@lru_cache()
+def get_settings():
+ return config.Settings()
+
+
+@app.get("/info")
+async def info(settings: Annotated[config.Settings, Depends(get_settings)]):
+ return {
+ "app_name": settings.app_name,
+ "admin_email": settings.admin_email,
+ "items_per_user": settings.items_per_user,
+ }
diff --git a/docs_src/settings/app03_an_py39/__init__.py b/docs_src/settings/app03_an_py39/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs_src/settings/app03_an_py39/config.py b/docs_src/settings/app03_an_py39/config.py
new file mode 100644
index 000000000..e1c3ee300
--- /dev/null
+++ b/docs_src/settings/app03_an_py39/config.py
@@ -0,0 +1,10 @@
+from pydantic import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "Awesome API"
+ admin_email: str
+ items_per_user: int = 50
+
+ class Config:
+ env_file = ".env"
diff --git a/docs_src/settings/app03_an_py39/main.py b/docs_src/settings/app03_an_py39/main.py
new file mode 100644
index 000000000..b89c6b6cf
--- /dev/null
+++ b/docs_src/settings/app03_an_py39/main.py
@@ -0,0 +1,22 @@
+from functools import lru_cache
+
+from fastapi import Depends, FastAPI
+from typing_extensions import Annotated
+
+from . import config
+
+app = FastAPI()
+
+
+@lru_cache()
+def get_settings():
+ return config.Settings()
+
+
+@app.get("/info")
+async def info(settings: Annotated[config.Settings, Depends(get_settings)]):
+ return {
+ "app_name": settings.app_name,
+ "admin_email": settings.admin_email,
+ "items_per_user": settings.items_per_user,
+ }
diff --git a/docs_src/websockets/tutorial002_an.py b/docs_src/websockets/tutorial002_an.py
new file mode 100644
index 000000000..c838fbd30
--- /dev/null
+++ b/docs_src/websockets/tutorial002_an.py
@@ -0,0 +1,93 @@
+from typing import Union
+
+from fastapi import (
+ Cookie,
+ Depends,
+ FastAPI,
+ Query,
+ WebSocket,
+ WebSocketException,
+ status,
+)
+from fastapi.responses import HTMLResponse
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+html = """
+
+
+
+