diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 47ef7c07fc..0000000000 --- a/.flake8 +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -max-line-length = 88 -select = C,E,F,W,B,B9 -ignore = E203, E501, W503 -exclude = __init__.py 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 3b16b4ad0c..3726b7d18d 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 55749398fd..a8f4c4de2d 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 322b6536a7..0000000000 --- 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 0000000000..c01e34b6dd --- /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/comment-docs-preview-in-pr/app/main.py b/.github/actions/comment-docs-preview-in-pr/app/main.py index c9fb7cbbef..68914fdb9a 100644 --- a/.github/actions/comment-docs-preview-in-pr/app/main.py +++ b/.github/actions/comment-docs-preview-in-pr/app/main.py @@ -1,7 +1,7 @@ import logging import sys from pathlib import Path -from typing import Optional +from typing import Union import httpx from github import Github @@ -14,7 +14,7 @@ github_api = "https://api.github.com" class Settings(BaseSettings): github_repository: str github_event_path: Path - github_event_name: Optional[str] = None + github_event_name: Union[str, None] = None input_token: SecretStr input_deploy_url: str @@ -42,7 +42,7 @@ if __name__ == "__main__": except ValidationError as e: logging.error(f"Error parsing event file: {e.errors()}") sys.exit(0) - use_pr: Optional[PullRequest] = None + use_pr: Union[PullRequest, None] = None for pr in repo.get_pulls(): if pr.head.sha == event.workflow_run.head_commit.id: use_pr = pr diff --git a/.github/actions/notify-translations/app/main.py b/.github/actions/notify-translations/app/main.py index 823685e00b..494fe6ad8e 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, Optional +from typing import Any, Dict, List, Union, cast -import yaml +import httpx from github import Github from pydantic import BaseModel, BaseSettings, SecretStr @@ -13,13 +14,173 @@ 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: Optional[str] = None - input_debug: Optional[bool] = False + github_event_name: Union[str, None] = None + httpx_timeout: int = 30 + input_debug: Union[bool, None] = False class PartialGitHubEventIssue(BaseModel): @@ -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: - logging.info( - 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}" - ) - 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: + 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"Changing labels in a closed PR doesn't trigger comments, PR: {pr.number}" + 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}" + ) + if already_notified_comment: + logging.info( + f"This PR #{pr.number} was already notified in comment: {already_notified_comment.url}" + ) + 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 0e5093f3ac..0000000000 --- a/.github/actions/notify-translations/app/translations.yml +++ /dev/null @@ -1,17 +0,0 @@ -pt: 1211 -es: 1218 -zh: 1228 -ru: 1362 -it: 1556 -ja: 1572 -uk: 1748 -tr: 1892 -fr: 1972 -ko: 2017 -sq: 2041 -pl: 3169 -de: 3716 -id: 3717 -az: 3994 -nl: 4701 -uz: 4883 diff --git a/.github/actions/people/app/main.py b/.github/actions/people/app/main.py index 9de6fc2505..2bf59f25e6 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, Optional, Set +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,45 +175,92 @@ class Author(BaseModel): url: str +# Issues and Discussions + + class CommentsNode(BaseModel): createdAt: datetime - author: Optional[Author] = None + 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: Optional[Author] = None + author: Union[Author, None] = None title: str createdAt: datetime state: str 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 @@ -179,7 +270,7 @@ class Labels(BaseModel): class ReviewNode(BaseModel): - author: Optional[Author] = None + author: Union[Author, None] = None state: str @@ -190,7 +281,7 @@ class Reviews(BaseModel): class PullRequestNode(BaseModel): number: int labels: Labels - author: Optional[Author] = None + author: Union[Author, None] = None title: str createdAt: datetime state: str @@ -219,6 +310,9 @@ class PRsResponse(BaseModel): data: PRsResponseData +# Sponsors + + class SponsorEntity(BaseModel): login: str avatarUrl: str @@ -260,45 +354,74 @@ class Settings(BaseSettings): input_token: SecretStr input_standard_token: SecretStr github_repository: str + httpx_timeout: int = 30 def get_graphql_response( - *, settings: Settings, query: str, after: Optional[str] = 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, + timeout=settings.httpx_timeout, json={"query": query, "variables": variables, "operationName": "Q"}, ) - if not response.status_code == 200: - logging.error(f"Response was not 200, after: {after}") + 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 data -def get_graphql_issue_edges(*, settings: Settings, after: Optional[str] = None): +def get_graphql_issue_edges(*, settings: Settings, after: Union[str, None] = None): data = get_graphql_response(settings=settings, query=issues_query, after=after) graphql_response = IssuesResponse.parse_obj(data) return graphql_response.data.repository.issues.edges -def get_graphql_pr_edges(*, settings: Settings, after: Optional[str] = None): +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) return graphql_response.data.repository.pullRequests.edges -def get_graphql_sponsor_edges(*, settings: Settings, after: Optional[str] = None): +def get_graphql_sponsor_edges(*, settings: Settings, after: Union[str, None] = None): data = get_graphql_response(settings=settings, query=sponsors_query, after=after) graphql_response = SponsorsResponse.parse_obj(data) 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) @@ -324,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 @@ -423,22 +611,22 @@ 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"} + bot_names = {"codecov", "github-actions", "pre-commit-ci", "dependabot"} maintainers = [] for login in maintainers_logins: user = authors[login] maintainers.append( { "login": login, - "answers": issue_commentors[login], + "answers": question_commentors[login], "prs": contributors[login], "avatarUrl": user.avatarUrl, "url": user.url, @@ -451,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/actions/watch-previews/Dockerfile b/.github/actions/watch-previews/Dockerfile deleted file mode 100644 index b8cc64d948..0000000000 --- a/.github/actions/watch-previews/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.7 - -RUN pip install httpx PyGithub "pydantic==1.5.1" - -COPY ./app /app - -CMD ["python", "/app/main.py"] diff --git a/.github/actions/watch-previews/action.yml b/.github/actions/watch-previews/action.yml deleted file mode 100644 index 5c09ad4876..0000000000 --- a/.github/actions/watch-previews/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Watch docs previews in PRs" -description: "Check PRs and trigger new docs deploys" -author: "Sebastián Ramírez " -inputs: - token: - description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}' - required: true -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/watch-previews/app/main.py b/.github/actions/watch-previews/app/main.py deleted file mode 100644 index 3b3520599c..0000000000 --- a/.github/actions/watch-previews/app/main.py +++ /dev/null @@ -1,101 +0,0 @@ -import logging -from datetime import datetime -from pathlib import Path -from typing import List, Optional - -import httpx -from github import Github -from github.NamedUser import NamedUser -from pydantic import BaseModel, BaseSettings, SecretStr - -github_api = "https://api.github.com" -netlify_api = "https://api.netlify.com" - - -class Settings(BaseSettings): - input_token: SecretStr - github_repository: str - github_event_path: Path - github_event_name: Optional[str] = None - - -class Artifact(BaseModel): - id: int - node_id: str - name: str - size_in_bytes: int - url: str - archive_download_url: str - expired: bool - created_at: datetime - updated_at: datetime - - -class ArtifactResponse(BaseModel): - total_count: int - artifacts: List[Artifact] - - -def get_message(commit: str) -> str: - return f"Docs preview for commit {commit} at" - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - settings = Settings() - logging.info(f"Using config: {settings.json()}") - g = Github(settings.input_token.get_secret_value()) - repo = g.get_repo(settings.github_repository) - owner: NamedUser = repo.owner - headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} - prs = list(repo.get_pulls(state="open")) - response = httpx.get( - f"{github_api}/repos/{settings.github_repository}/actions/artifacts", - headers=headers, - ) - data = response.json() - artifacts_response = ArtifactResponse.parse_obj(data) - for pr in prs: - logging.info("-----") - logging.info(f"Processing PR #{pr.number}: {pr.title}") - pr_comments = list(pr.get_issue_comments()) - pr_commits = list(pr.get_commits()) - last_commit = pr_commits[0] - for pr_commit in pr_commits: - if pr_commit.commit.author.date > last_commit.commit.author.date: - last_commit = pr_commit - commit = last_commit.commit.sha - logging.info(f"Last commit: {commit}") - message = get_message(commit) - notified = False - for pr_comment in pr_comments: - if message in pr_comment.body: - notified = True - logging.info(f"Docs preview was notified: {notified}") - if not notified: - artifact_name = f"docs-zip-{commit}" - use_artifact: Optional[Artifact] = None - for artifact in artifacts_response.artifacts: - if artifact.name == artifact_name: - use_artifact = artifact - break - if not use_artifact: - logging.info("Artifact not available") - else: - logging.info(f"Existing artifact: {use_artifact.name}") - response = httpx.post( - "https://api.github.com/repos/tiangolo/fastapi/actions/workflows/preview-docs.yml/dispatches", - headers=headers, - json={ - "ref": "master", - "inputs": { - "pr": f"{pr.number}", - "name": artifact_name, - "commit": commit, - }, - }, - ) - logging.info( - f"Trigger sent, response status: {response.status_code} - content: {response.content}" - ) - logging.info("Finished") diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..cd972a0ba4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ + # Python + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆ diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 505d66f9f1..a155ecfeca 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -4,49 +4,111 @@ on: branches: - master pull_request: - types: [opened, synchronize] + types: + - opened + - synchronize jobs: + changes: + runs-on: ubuntu-latest + # Required permissions + permissions: + pull-requests: read + # Set job outputs to values from filter step + outputs: + docs: ${{ steps.filter.outputs.docs }} + steps: + - uses: actions/checkout@v3 + # For pull requests it's not necessary to checkout the code but for master it is + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + docs: + - README.md + - docs/** + - docs_src/** + - requirements-docs.txt + langs: + needs: + - changes + runs-on: ubuntu-latest + outputs: + langs: ${{ steps.show-langs.outputs.langs }} + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - uses: actions/cache@v3 + id: cache + with: + path: ${{ env.pythonLocation }} + key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v05 + - name: Install docs extras + if: steps.cache.outputs.cache-hit != 'true' + run: pip install -r requirements-docs.txt + # Install MkDocs Material Insiders here just to put it in the cache for the rest of the steps + - name: Install Material for MkDocs Insiders + if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true' + run: pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git + - name: Export Language Codes + id: show-langs + run: | + echo "langs=$(python ./scripts/docs.py langs-json)" >> $GITHUB_OUTPUT + build-docs: - runs-on: ubuntu-18.04 + needs: + - changes + - langs + if: ${{ needs.changes.outputs.docs == 'true' }} + runs-on: ubuntu-latest + strategy: + matrix: + lang: ${{ fromJson(needs.langs.outputs.langs) }} steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: "3.7" - - uses: actions/cache@v2 + python-version: "3.11" + - uses: actions/cache@v3 id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v03 - - name: Install Flit - if: steps.cache.outputs.cache-hit != 'true' - run: python3.7 -m pip install flit + key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v05 - name: Install docs extras if: steps.cache.outputs.cache-hit != 'true' - run: python3.7 -m flit install --deps production --extras doc + run: pip install -r requirements-docs.txt - name: Install Material for MkDocs Insiders if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true' run: pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git + - name: Update Languages + run: python ./scripts/docs.py update-languages + - uses: actions/cache@v3 + with: + key: mkdocs-cards-${{ matrix.lang }}-${{ github.ref }} + path: docs/${{ matrix.lang }}/.cache - name: Build Docs - run: python3.7 ./scripts/docs.py build-all - - name: Zip docs - run: bash ./scripts/zip-docs.sh - - uses: actions/upload-artifact@v2 + run: python ./scripts/docs.py build-lang ${{ matrix.lang }} + - uses: actions/upload-artifact@v3 with: - name: docs-zip - path: ./docs.zip - - name: Deploy to Netlify - uses: nwtgck/actions-netlify@v1.1.5 + name: docs-site + path: ./site/** + + # https://github.com/marketplace/actions/alls-green#why + docs-all-green: # This job does nothing and is only used for the branch protection + if: always() + needs: + - build-docs + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 with: - publish-dir: './site' - production-branch: master - github-token: ${{ secrets.GITHUB_TOKEN }} - enable-commit-comment: false - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} + jobs: ${{ toJSON(needs) }} + allowed-skips: build-docs diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000000..312d835af8 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,49 @@ +name: Deploy Docs +on: + workflow_run: + workflows: + - Build Docs + types: + - completed + +jobs: + deploy-docs: + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v3 + - name: Clean site + run: | + rm -rf ./site + mkdir ./site + - name: Download Artifact Docs + id: download + uses: dawidd6/action-download-artifact@v2.27.0 + with: + if_no_artifact_found: ignore + github_token: ${{ secrets.FASTAPI_PREVIEW_DOCS_DOWNLOAD_ARTIFACTS }} + workflow: build-docs.yml + run_id: ${{ github.event.workflow_run.id }} + name: docs-site + path: ./site/ + - name: Deploy to Netlify + if: steps.download.outputs.found_artifact == 'true' + id: netlify + uses: nwtgck/actions-netlify@v2.0.0 + with: + publish-dir: './site' + production-deploy: ${{ github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' }} + github-token: ${{ secrets.FASTAPI_PREVIEW_DOCS_NETLIFY }} + enable-commit-comment: false + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} + - name: Comment Deploy + if: steps.netlify.outputs.deploy-url != '' + uses: ./.github/actions/comment-docs-preview-in-pr + with: + token: ${{ secrets.FASTAPI_PREVIEW_DOCS_COMMENT_DEPLOY }} + deploy_url: "${{ steps.netlify.outputs.deploy-url }}" diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index e2fb4f7a43..3246231031 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -2,7 +2,7 @@ name: Issue Manager on: schedule: - - cron: "0 0 * * *" + - cron: "10 3 * * *" issue_comment: types: - created @@ -16,11 +16,12 @@ on: jobs: issue-manager: + if: github.repository_owner == 'tiangolo' runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.4.0 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.FASTAPI_ISSUE_MANAGER }} config: > { "answered": { diff --git a/.github/workflows/label-approved.yml b/.github/workflows/label-approved.yml index b2646dd16d..976d29f74d 100644 --- a/.github/workflows/label-approved.yml +++ b/.github/workflows/label-approved.yml @@ -6,8 +6,9 @@ on: jobs: label-approved: + if: github.repository_owner == 'tiangolo' runs-on: ubuntu-latest steps: - uses: docker://tiangolo/label-approved:0.0.2 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.FASTAPI_LABEL_APPROVED }} diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 5783c993a0..f11a638487 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -20,7 +20,7 @@ jobs: latest-changes: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: # To allow latest-changes to commit to master token: ${{ secrets.ACTIONS_TOKEN }} @@ -30,11 +30,9 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} with: limit-access-to-actor: true - token: ${{ secrets.ACTIONS_TOKEN }} - standard_token: ${{ secrets.GITHUB_TOKEN }} - uses: docker://tiangolo/latest-changes:0.0.3 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.FASTAPI_LATEST_CHANGES }} latest_changes_file: docs/en/docs/release-notes.md latest_changes_header: '## Latest Changes\n\n' debug_logs: true diff --git a/.github/workflows/notify-translations.yml b/.github/workflows/notify-translations.yml index 7e414ab95e..0926486e9b 100644 --- a/.github/workflows/notify-translations.yml +++ b/.github/workflows/notify-translations.yml @@ -4,12 +4,13 @@ on: pull_request_target: types: - labeled + - closed jobs: notify-translations: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 @@ -18,4 +19,4 @@ jobs: limit-access-to-actor: true - uses: ./.github/actions/notify-translations with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.FASTAPI_NOTIFY_TRANSLATIONS }} diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml index 2004ee7b3a..15ea464a1f 100644 --- a/.github/workflows/people.yml +++ b/.github/workflows/people.yml @@ -12,18 +12,20 @@ on: jobs: fastapi-people: + if: github.repository_owner == 'tiangolo' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - 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 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} with: limit-access-to-actor: true - token: ${{ secrets.ACTIONS_TOKEN }} - standard_token: ${{ secrets.GITHUB_TOKEN }} - uses: ./.github/actions/people with: token: ${{ secrets.ACTIONS_TOKEN }} - standard_token: ${{ secrets.GITHUB_TOKEN }} + standard_token: ${{ secrets.FASTAPI_PEOPLE }} diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml deleted file mode 100644 index 104c2677f4..0000000000 --- a/.github/workflows/preview-docs.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Preview Docs -on: - workflow_run: - workflows: - - Build Docs - types: - - completed - -jobs: - preview-docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Download Artifact Docs - uses: dawidd6/action-download-artifact@v2.9.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - workflow: build-docs.yml - run_id: ${{ github.event.workflow_run.id }} - name: docs-zip - - name: Unzip docs - run: | - rm -rf ./site - unzip docs.zip - rm -f docs.zip - - name: Deploy to Netlify - id: netlify - uses: nwtgck/actions-netlify@v1.1.5 - with: - publish-dir: './site' - production-deploy: false - github-token: ${{ secrets.GITHUB_TOKEN }} - enable-commit-comment: false - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - - name: Comment Deploy - uses: ./.github/actions/comment-docs-preview-in-pr - with: - token: ${{ secrets.GITHUB_TOKEN }} - deploy_url: "${{ steps.netlify.outputs.deploy-url }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9dde4e066c..b84c5bf17a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,34 +13,29 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: "3.6" - - uses: actions/cache@v2 + python-version: "3.7" + # Issue ref: https://github.com/actions/setup-python/issues/436 + # cache: "pip" + cache-dependency-path: pyproject.toml + - uses: actions/cache@v3 id: cache with: path: ${{ env.pythonLocation }} key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-publish - - name: Install Flit + - name: Install build dependencies if: steps.cache.outputs.cache-hit != 'true' - run: pip install flit - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: flit install --symlink + run: pip install build + - name: Build distribution + run: python -m build - name: Publish - env: - FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }} - FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }} - run: bash scripts/publish.sh + uses: pypa/gh-action-pypi-publish@v1.8.6 + with: + password: ${{ secrets.PYPI_API_TOKEN }} - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - # - name: Notify - # env: - # GITTER_TOKEN: ${{ secrets.GITTER_TOKEN }} - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # TAG: ${{ github.event.release.name }} - # run: bash scripts/notify.sh diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml new file mode 100644 index 0000000000..c6d894d9f6 --- /dev/null +++ b/.github/workflows/smokeshow.yml @@ -0,0 +1,36 @@ +name: Smokeshow + +on: + workflow_run: + workflows: [Test] + types: [completed] + +permissions: + statuses: write + +jobs: + smokeshow: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - run: pip install smokeshow + + - uses: dawidd6/action-download-artifact@v2.27.0 + with: + github_token: ${{ secrets.FASTAPI_SMOKESHOW_DOWNLOAD_ARTIFACTS }} + workflow: test.yml + commit: ${{ github.event.workflow_run.head_sha }} + + - run: smokeshow upload coverage-html + env: + SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} + SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100 + SMOKESHOW_GITHUB_CONTEXT: coverage + SMOKESHOW_GITHUB_TOKEN: ${{ secrets.FASTAPI_SMOKESHOW_UPLOAD }} + SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0a82344e4..84f101424e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,37 +5,103 @@ on: branches: - master pull_request: - types: [opened, synchronize] + types: + - opened + - synchronize jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + # Issue ref: https://github.com/actions/setup-python/issues/436 + # cache: "pip" + # cache-dependency-path: pyproject.toml + - uses: actions/cache@v3 + id: cache + with: + path: ${{ env.pythonLocation }} + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03 + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: pip install -r requirements-tests.txt + - name: Lint + run: bash scripts/lint.sh + test: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] fail-fast: false - steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - uses: actions/cache@v2 + # Issue ref: https://github.com/actions/setup-python/issues/436 + # cache: "pip" + # cache-dependency-path: pyproject.toml + - uses: actions/cache@v3 id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v02 - - name: Install Flit - if: steps.cache.outputs.cache-hit != 'true' - run: pip install flit + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03 - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' - run: flit install --symlink - - name: Lint - if: ${{ matrix.python-version != '3.6' }} - run: bash scripts/lint.sh + run: pip install -r requirements-tests.txt + - run: mkdir coverage - name: Test run: bash scripts/test.sh - - name: Upload coverage - uses: codecov/codecov-action@v2 + env: + COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} + CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} + - name: Store coverage files + uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage + + coverage-combine: + needs: [test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.8' + # Issue ref: https://github.com/actions/setup-python/issues/436 + # cache: "pip" + # cache-dependency-path: pyproject.toml + - name: Get coverage files + uses: actions/download-artifact@v3 + with: + name: coverage + path: coverage + - run: pip install coverage[toml] + - run: ls -la coverage + - run: coverage combine coverage + - run: coverage report + - run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}" + - name: Store coverage HTML + uses: actions/upload-artifact@v3 + 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/.gitignore b/.gitignore index a26bb5cd64..d380d16b7d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ Pipfile.lock env3.* env docs_build +site_build venv docs.zip archive.zip @@ -23,3 +24,4 @@ archive.zip # vim temporary files *~ .*.sw? +.cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c278571e6..2a8a031363 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.2.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-toml @@ -12,38 +14,20 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v3.3.1 hooks: - id: pyupgrade args: - --py3-plus - --keep-runtime-typing -- repo: https://github.com/myint/autoflake - rev: v1.4 +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.272 hooks: - - id: autoflake + - id: ruff args: - - --recursive - - --in-place - - --remove-all-unused-imports - - --remove-unused-variables - - --expand-star-imports - - --exclude - - __init__.py - - --remove-duplicate-keys -- repo: https://github.com/pycqa/isort - rev: 5.10.1 - hooks: - - id: isort - name: isort (python) - - id: isort - name: isort (cython) - types: [cython] - - id: isort - name: isort (pyi) - types: [pyi] + - --fix - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black ci: diff --git a/README.md b/README.md index 9ad50f271f..7dc199367e 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ Test - - Coverage + + Coverage Package version @@ -27,12 +27,11 @@ --- -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. +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. @@ -47,17 +46,15 @@ The key features are: - - - + - - - + + + @@ -103,6 +100,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._" + +
Deon Pillsbury - Cisco (ref)
+ +--- + ## **Typer**, the FastAPI of CLIs @@ -113,7 +116,7 @@ If you are building a CLI app to be ## Requirements -Python 3.6+ +Python 3.7+ FastAPI stands on the shoulders of giants: @@ -132,7 +135,7 @@ $ pip install fastapi -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. +You will also need an ASGI server, for production such as Uvicorn or Hypercorn.
@@ -151,7 +154,7 @@ $ pip install "uvicorn[standard]" * Create a file `main.py` with: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -164,7 +167,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -174,7 +177,7 @@ def read_item(item_id: int, q: Optional[str] = None): If your code uses `async` / `await`, use `async def`: ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -187,7 +190,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -266,7 +269,7 @@ Now modify the file `main.py` to receive a body from a `PUT` request. Declare the body using standard Python types, thanks to Pydantic. ```Python hl_lines="4 9-12 25-27" -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -277,7 +280,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -286,7 +289,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -329,7 +332,7 @@ You do that with standard modern Python types. You don't have to learn a new syntax, the methods or classes of a specific library, etc. -Just standard **Python 3.6+**. +Just standard **Python 3.7+**. For example, for an `int`: @@ -428,7 +431,7 @@ For a more complete example including more features, see the Strawberry and other libraries. * Many extra features (thanks to Starlette) as: * **WebSockets** - * extremely easy tests based on `requests` and `pytest` + * extremely easy tests based on HTTPX and `pytest` * **CORS** * **Cookie Sessions** * ...and more. @@ -443,12 +446,11 @@ To understand more about it, see the section ujson - for faster JSON "parsing". * email_validator - for email validation. Used by Starlette: -* requests - Required if you want to use the `TestClient`. +* 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. diff --git a/SECURITY.md b/SECURITY.md index 322f95f629..db412cf2c8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ Learn more about it below. 👇 ## Versions -The latest versions of FastAPI are supported. +The latest version of FastAPI is supported. You are encouraged to [write tests](https://fastapi.tiangolo.com/tutorial/testing/) for your application and update your FastAPI version frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**. diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml deleted file mode 100644 index 60bd8eaad6..0000000000 --- a/docs/az/mkdocs.yml +++ /dev/null @@ -1,137 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/az/ -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - 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 -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: /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: /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/de/docs/features.md b/docs/de/docs/features.md index d99ade4024..f281afd1ed 100644 --- a/docs/de/docs/features.md +++ b/docs/de/docs/features.md @@ -97,7 +97,7 @@ Hierdurch werden Sie nie wieder einen falschen Schlüsselnamen benutzen und spar ### Kompakt -FastAPI nutzt für alles sinnvolle **Standard-Einstellungen**, welche optional überall konfiguriert werden können. Alle Parameter können ganz genau an Ihre Bedürfnisse angepasst werden, sodass sie genau die API definieren können, die sie brauchen. +FastAPI nutzt für alles sensible **Standard-Einstellungen**, welche optional überall konfiguriert werden können. Alle Parameter können ganz genau an Ihre Bedürfnisse angepasst werden, sodass sie genau die API definieren können, die sie brauchen. Aber standardmäßig, **"funktioniert einfach"** alles. @@ -119,9 +119,9 @@ Die gesamte Validierung übernimmt das etablierte und robuste **Pydantic**. ### Sicherheit und Authentifizierung -Sicherheit und Authentifizierung integriert. Ohne einen Kompromiss aufgrund einer Datenbank oder den Datenentitäten. +Integrierte Sicherheit und Authentifizierung. Ohne Kompromisse bei Datenbanken oder Datenmodellen. -Unterstützt alle von OpenAPI definierten Sicherheitsschemata, hierzu gehören: +Unterstützt werden alle von OpenAPI definierten Sicherheitsschemata, hierzu gehören: * HTTP Basis Authentifizierung. * **OAuth2** (auch mit **JWT Zugriffstokens**). Schauen Sie sich hierzu dieses Tutorial an: [OAuth2 mit JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. @@ -142,8 +142,8 @@ FastAPI enthält ein extrem einfaches, aber extrem mächtiges eines der schnellsten Python frameworks, auf Augenhöhe mit **NodeJS** und **Go**. +* Stark beeindruckende Performanz. Es ist eines der schnellsten Python Frameworks, auf Augenhöhe mit **NodeJS** und **Go**. * **WebSocket**-Unterstützung. * Hintergrundaufgaben im selben Prozess. * Ereignisse für das Starten und Herunterfahren. -* Testclient basierend auf `requests`. +* Testclient basierend auf HTTPX. * **CORS**, GZip, statische Dateien, Antwortfluss. * **Sitzungs und Cookie** Unterstützung. * 100% Testabdeckung. @@ -196,8 +196,8 @@ Mit **FastAPI** bekommen Sie alle Funktionen von **Pydantic** (da FastAPI für d * In Vergleichen ist Pydantic schneller als jede andere getestete Bibliothek. * Validierung von **komplexen Strukturen**: * Benutzung von hierachischen Pydantic Schemata, Python `typing`’s `List` und `Dict`, etc. - * Validierungen erlauben klare und einfache Datenschemadefinition, überprüft und dokumentiert als JSON Schema. + * Validierungen erlauben eine klare und einfache Datenschemadefinition, überprüft und dokumentiert als JSON Schema. * Sie können stark **verschachtelte JSON** Objekte haben und diese sind trotzdem validiert und annotiert. * **Erweiterbar**: - * Pydantic erlaubt die Definition von eigenen Datentypen oder Sie können die Validierung mit einer `validator` dekorierten Methode erweitern.. + * Pydantic erlaubt die Definition von eigenen Datentypen oder sie können die Validierung mit einer `validator` dekorierten Methode erweitern. * 100% Testabdeckung. diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md index ce13bcc4a0..f1c873d75f 100644 --- a/docs/de/docs/index.md +++ b/docs/de/docs/index.md @@ -111,7 +111,7 @@ If you are building a CLI app to be ## Requirements -Python 3.6+ +Python 3.7+ FastAPI stands on the shoulders of giants: @@ -135,7 +135,7 @@ You will also need an ASGI server, for production such as ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -149,7 +149,7 @@ $ pip install uvicorn[standard] * Create a file `main.py` with: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -162,7 +162,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -172,7 +172,7 @@ def read_item(item_id: int, q: Optional[str] = None): If your code uses `async` / `await`, use `async def`: ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -185,7 +185,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -264,7 +264,7 @@ Now modify the file `main.py` to receive a body from a `PUT` request. Declare the body using standard Python types, thanks to Pydantic. ```Python hl_lines="4 9-12 25-27" -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -275,7 +275,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -284,7 +284,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -440,12 +440,11 @@ To understand more about it, see the section ujson - for faster JSON "parsing". * email_validator - for email validation. Used by Starlette: -* requests - Required if you want to use the `TestClient`. +* 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. diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml index c72f325f62..de18856f44 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -1,138 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/de/ -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: de -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/em/docs/advanced/additional-responses.md b/docs/em/docs/advanced/additional-responses.md new file mode 100644 index 0000000000..26963c2e31 --- /dev/null +++ b/docs/em/docs/advanced/additional-responses.md @@ -0,0 +1,240 @@ +# 🌖 📨 🗄 + +!!! warning + 👉 👍 🏧 ❔. + + 🚥 👆 ▶️ ⏮️ **FastAPI**, 👆 💪 🚫 💪 👉. + +👆 💪 📣 🌖 📨, ⏮️ 🌖 👔 📟, 🔉 🆎, 📛, ♒️. + +👈 🌖 📨 🔜 🔌 🗄 🔗, 👫 🔜 😑 🛠️ 🩺. + +✋️ 👈 🌖 📨 👆 ✔️ ⚒ 💭 👆 📨 `Response` 💖 `JSONResponse` 🔗, ⏮️ 👆 👔 📟 & 🎚. + +## 🌖 📨 ⏮️ `model` + +👆 💪 🚶‍♀️ 👆 *➡ 🛠️ 👨‍🎨* 🔢 `responses`. + +⚫️ 📨 `dict`, 🔑 👔 📟 🔠 📨, 💖 `200`, & 💲 🎏 `dict`Ⓜ ⏮️ ℹ 🔠 👫. + +🔠 👈 📨 `dict`Ⓜ 💪 ✔️ 🔑 `model`, ⚗ Pydantic 🏷, 💖 `response_model`. + +**FastAPI** 🔜 ✊ 👈 🏷, 🏗 🚮 🎻 🔗 & 🔌 ⚫️ ☑ 🥉 🗄. + +🖼, 📣 ➕1️⃣ 📨 ⏮️ 👔 📟 `404` & Pydantic 🏷 `Message`, 👆 💪 ✍: + +```Python hl_lines="18 22" +{!../../../docs_src/additional_responses/tutorial001.py!} +``` + +!!! note + ✔️ 🤯 👈 👆 ✔️ 📨 `JSONResponse` 🔗. + +!!! info + `model` 🔑 🚫 🍕 🗄. + + **FastAPI** 🔜 ✊ Pydantic 🏷 ⚪️➡️ 📤, 🏗 `JSON Schema`, & 🚮 ⚫️ ☑ 🥉. + + ☑ 🥉: + + * 🔑 `content`, 👈 ✔️ 💲 ➕1️⃣ 🎻 🎚 (`dict`) 👈 🔌: + * 🔑 ⏮️ 📻 🆎, ✅ `application/json`, 👈 🔌 💲 ➕1️⃣ 🎻 🎚, 👈 🔌: + * 🔑 `schema`, 👈 ✔️ 💲 🎻 🔗 ⚪️➡️ 🏷, 📥 ☑ 🥉. + * **FastAPI** 🚮 🔗 📥 🌐 🎻 🔗 ➕1️⃣ 🥉 👆 🗄 ↩️ ✅ ⚫️ 🔗. 👉 🌌, 🎏 🈸 & 👩‍💻 💪 ⚙️ 👈 🎻 🔗 🔗, 🚚 👻 📟 ⚡ 🧰, ♒️. + +🏗 📨 🗄 👉 *➡ 🛠️* 🔜: + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Additional Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +🔗 🔗 ➕1️⃣ 🥉 🔘 🗄 🔗: + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## 🌖 🔉 🆎 👑 📨 + +👆 💪 ⚙️ 👉 🎏 `responses` 🔢 🚮 🎏 🔉 🆎 🎏 👑 📨. + +🖼, 👆 💪 🚮 🌖 📻 🆎 `image/png`, 📣 👈 👆 *➡ 🛠️* 💪 📨 🎻 🎚 (⏮️ 📻 🆎 `application/json`) ⚖️ 🇩🇴 🖼: + +```Python hl_lines="19-24 28" +{!../../../docs_src/additional_responses/tutorial002.py!} +``` + +!!! note + 👀 👈 👆 ✔️ 📨 🖼 ⚙️ `FileResponse` 🔗. + +!!! info + 🚥 👆 ✔ 🎏 📻 🆎 🎯 👆 `responses` 🔢, FastAPI 🔜 🤔 📨 ✔️ 🎏 📻 🆎 👑 📨 🎓 (🔢 `application/json`). + + ✋️ 🚥 👆 ✔️ ✔ 🛃 📨 🎓 ⏮️ `None` 🚮 📻 🆎, FastAPI 🔜 ⚙️ `application/json` 🙆 🌖 📨 👈 ✔️ 👨‍💼 🏷. + +## 🌀 ℹ + +👆 💪 🌀 📨 ℹ ⚪️➡️ 💗 🥉, 🔌 `response_model`, `status_code`, & `responses` 🔢. + +👆 💪 📣 `response_model`, ⚙️ 🔢 👔 📟 `200` (⚖️ 🛃 1️⃣ 🚥 👆 💪), & ⤴️ 📣 🌖 ℹ 👈 🎏 📨 `responses`, 🔗 🗄 🔗. + +**FastAPI** 🔜 🚧 🌖 ℹ ⚪️➡️ `responses`, & 🌀 ⚫️ ⏮️ 🎻 🔗 ⚪️➡️ 👆 🏷. + +🖼, 👆 💪 📣 📨 ⏮️ 👔 📟 `404` 👈 ⚙️ Pydantic 🏷 & ✔️ 🛃 `description`. + +& 📨 ⏮️ 👔 📟 `200` 👈 ⚙️ 👆 `response_model`, ✋️ 🔌 🛃 `example`: + +```Python hl_lines="20-31" +{!../../../docs_src/additional_responses/tutorial003.py!} +``` + +⚫️ 🔜 🌐 🌀 & 🔌 👆 🗄, & 🎦 🛠️ 🩺: + + + +## 🌀 🔢 📨 & 🛃 🕐 + +👆 💪 💚 ✔️ 🔁 📨 👈 ✔ 📚 *➡ 🛠️*, ✋️ 👆 💚 🌀 👫 ⏮️ 🛃 📨 💚 🔠 *➡ 🛠️*. + +📚 💼, 👆 💪 ⚙️ 🐍 ⚒ "🏗" `dict` ⏮️ `**dict_to_unpack`: + +```Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +📥, `new_dict` 🔜 🔌 🌐 🔑-💲 👫 ⚪️➡️ `old_dict` ➕ 🆕 🔑-💲 👫: + +```Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +👆 💪 ⚙️ 👈 ⚒ 🏤-⚙️ 🔢 📨 👆 *➡ 🛠️* & 🌀 👫 ⏮️ 🌖 🛃 🕐. + +🖼: + +```Python hl_lines="13-17 26" +{!../../../docs_src/additional_responses/tutorial004.py!} +``` + +## 🌖 ℹ 🔃 🗄 📨 + +👀 ⚫️❔ ⚫️❔ 👆 💪 🔌 📨, 👆 💪 ✅ 👉 📄 🗄 🔧: + +* 🗄 📨 🎚, ⚫️ 🔌 `Response Object`. +* 🗄 📨 🎚, 👆 💪 🔌 🕳 ⚪️➡️ 👉 🔗 🔠 📨 🔘 👆 `responses` 🔢. ✅ `description`, `headers`, `content` (🔘 👉 👈 👆 📣 🎏 🔉 🆎 & 🎻 🔗), & `links`. diff --git a/docs/em/docs/advanced/additional-status-codes.md b/docs/em/docs/advanced/additional-status-codes.md new file mode 100644 index 0000000000..392579df6e --- /dev/null +++ b/docs/em/docs/advanced/additional-status-codes.md @@ -0,0 +1,37 @@ +# 🌖 👔 📟 + +🔢, **FastAPI** 🔜 📨 📨 ⚙️ `JSONResponse`, 🚮 🎚 👆 📨 ⚪️➡️ 👆 *➡ 🛠️* 🔘 👈 `JSONResponse`. + +⚫️ 🔜 ⚙️ 🔢 👔 📟 ⚖️ 1️⃣ 👆 ⚒ 👆 *➡ 🛠️*. + +## 🌖 👔 📟 + +🚥 👆 💚 📨 🌖 👔 📟 ↖️ ⚪️➡️ 👑 1️⃣, 👆 💪 👈 🛬 `Response` 🔗, 💖 `JSONResponse`, & ⚒ 🌖 👔 📟 🔗. + +🖼, ➡️ 💬 👈 👆 💚 ✔️ *➡ 🛠️* 👈 ✔ ℹ 🏬, & 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣0️⃣ "👌" 🕐❔ 🏆. + +✋️ 👆 💚 ⚫️ 🚫 🆕 🏬. & 🕐❔ 🏬 🚫 🔀 ⏭, ⚫️ ✍ 👫, & 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣1️⃣ "✍". + +🏆 👈, 🗄 `JSONResponse`, & 📨 👆 🎚 📤 🔗, ⚒ `status_code` 👈 👆 💚: + +```Python hl_lines="4 25" +{!../../../docs_src/additional_status_codes/tutorial001.py!} +``` + +!!! warning + 🕐❔ 👆 📨 `Response` 🔗, 💖 🖼 🔛, ⚫️ 🔜 📨 🔗. + + ⚫️ 🏆 🚫 🎻 ⏮️ 🏷, ♒️. + + ⚒ 💭 ⚫️ ✔️ 📊 👆 💚 ⚫️ ✔️, & 👈 💲 ☑ 🎻 (🚥 👆 ⚙️ `JSONResponse`). + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.responses import JSONResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `status`. + +## 🗄 & 🛠️ 🩺 + +🚥 👆 📨 🌖 👔 📟 & 📨 🔗, 👫 🏆 🚫 🔌 🗄 🔗 (🛠️ 🩺), ↩️ FastAPI 🚫 ✔️ 🌌 💭 ⏪ ⚫️❔ 👆 🚶 📨. + +✋️ 👆 💪 📄 👈 👆 📟, ⚙️: [🌖 📨](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/advanced-dependencies.md b/docs/em/docs/advanced/advanced-dependencies.md new file mode 100644 index 0000000000..fa1554734c --- /dev/null +++ b/docs/em/docs/advanced/advanced-dependencies.md @@ -0,0 +1,70 @@ +# 🏧 🔗 + +## 🔗 🔗 + +🌐 🔗 👥 ✔️ 👀 🔧 🔢 ⚖️ 🎓. + +✋️ 📤 💪 💼 🌐❔ 👆 💚 💪 ⚒ 🔢 🔛 🔗, 🍵 ✔️ 📣 📚 🎏 🔢 ⚖️ 🎓. + +➡️ 🌈 👈 👥 💚 ✔️ 🔗 👈 ✅ 🚥 🔢 🔢 `q` 🔌 🔧 🎚. + +✋️ 👥 💚 💪 🔗 👈 🔧 🎚. + +## "🇧🇲" 👐 + +🐍 📤 🌌 ⚒ 👐 🎓 "🇧🇲". + +🚫 🎓 ⚫️ (❔ ⏪ 🇧🇲), ✋️ 👐 👈 🎓. + +👈, 👥 📣 👩‍🔬 `__call__`: + +```Python hl_lines="10" +{!../../../docs_src/dependencies/tutorial011.py!} +``` + +👉 💼, 👉 `__call__` ⚫️❔ **FastAPI** 🔜 ⚙️ ✅ 🌖 🔢 & 🎧-🔗, & 👉 ⚫️❔ 🔜 🤙 🚶‍♀️ 💲 🔢 👆 *➡ 🛠️ 🔢* ⏪. + +## 🔗 👐 + +& 🔜, 👥 💪 ⚙️ `__init__` 📣 🔢 👐 👈 👥 💪 ⚙️ "🔗" 🔗: + +```Python hl_lines="7" +{!../../../docs_src/dependencies/tutorial011.py!} +``` + +👉 💼, **FastAPI** 🏆 🚫 ⏱ 👆 ⚖️ 💅 🔃 `__init__`, 👥 🔜 ⚙️ ⚫️ 🔗 👆 📟. + +## ✍ 👐 + +👥 💪 ✍ 👐 👉 🎓 ⏮️: + +```Python hl_lines="16" +{!../../../docs_src/dependencies/tutorial011.py!} +``` + +& 👈 🌌 👥 💪 "🔗" 👆 🔗, 👈 🔜 ✔️ `"bar"` 🔘 ⚫️, 🔢 `checker.fixed_content`. + +## ⚙️ 👐 🔗 + +⤴️, 👥 💪 ⚙️ 👉 `checker` `Depends(checker)`, ↩️ `Depends(FixedContentQueryChecker)`, ↩️ 🔗 👐, `checker`, 🚫 🎓 ⚫️. + +& 🕐❔ ❎ 🔗, **FastAPI** 🔜 🤙 👉 `checker` 💖: + +```Python +checker(q="somequery") +``` + +...& 🚶‍♀️ ⚫️❔ 👈 📨 💲 🔗 👆 *➡ 🛠️ 🔢* 🔢 `fixed_content_included`: + +```Python hl_lines="20" +{!../../../docs_src/dependencies/tutorial011.py!} +``` + +!!! tip + 🌐 👉 💪 😑 🎭. & ⚫️ 💪 🚫 📶 🆑 ❔ ⚫️ ⚠. + + 👫 🖼 😫 🙅, ✋️ 🎦 ❔ ⚫️ 🌐 👷. + + 📃 🔃 💂‍♂, 📤 🚙 🔢 👈 🛠️ 👉 🎏 🌌. + + 🚥 👆 🤔 🌐 👉, 👆 ⏪ 💭 ❔ 👈 🚙 🧰 💂‍♂ 👷 🔘. diff --git a/docs/em/docs/advanced/async-sql-databases.md b/docs/em/docs/advanced/async-sql-databases.md new file mode 100644 index 0000000000..848936de16 --- /dev/null +++ b/docs/em/docs/advanced/async-sql-databases.md @@ -0,0 +1,162 @@ +# 🔁 🗄 (🔗) 💽 + +👆 💪 ⚙️ `encode/databases` ⏮️ **FastAPI** 🔗 💽 ⚙️ `async` & `await`. + +⚫️ 🔗 ⏮️: + +* ✳ +* ✳ +* 🗄 + +👉 🖼, 👥 🔜 ⚙️ **🗄**, ↩️ ⚫️ ⚙️ 👁 📁 & 🐍 ✔️ 🛠️ 🐕‍🦺. , 👆 💪 📁 👉 🖼 & 🏃 ⚫️. + +⏪, 👆 🏭 🈸, 👆 💪 💚 ⚙️ 💽 💽 💖 **✳**. + +!!! tip + 👆 💪 🛠️ 💭 ⚪️➡️ 📄 🔃 🇸🇲 🐜 ([🗄 (🔗) 💽](../tutorial/sql-databases.md){.internal-link target=_blank}), 💖 ⚙️ 🚙 🔢 🎭 🛠️ 💽, 🔬 👆 **FastAPI** 📟. + + 👉 📄 🚫 ✔ 📚 💭, 🌓 😑 💃. + +## 🗄 & ⚒ 🆙 `SQLAlchemy` + +* 🗄 `SQLAlchemy`. +* ✍ `metadata` 🎚. +* ✍ 🏓 `notes` ⚙️ `metadata` 🎚. + +```Python hl_lines="4 14 16-22" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! tip + 👀 👈 🌐 👉 📟 😁 🇸🇲 🐚. + + `databases` 🚫 🔨 🕳 📥. + +## 🗄 & ⚒ 🆙 `databases` + +* 🗄 `databases`. +* ✍ `DATABASE_URL`. +* ✍ `database` 🎚. + +```Python hl_lines="3 9 12" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! tip + 🚥 👆 🔗 🎏 💽 (✅ ✳), 👆 🔜 💪 🔀 `DATABASE_URL`. + +## ✍ 🏓 + +👉 💼, 👥 🏗 🏓 🎏 🐍 📁, ✋️ 🏭, 👆 🔜 🎲 💚 ✍ 👫 ⏮️ ⚗, 🛠️ ⏮️ 🛠️, ♒️. + +📥, 👉 📄 🔜 🏃 🔗, ▶️️ ⏭ ▶️ 👆 **FastAPI** 🈸. + +* ✍ `engine`. +* ✍ 🌐 🏓 ⚪️➡️ `metadata` 🎚. + +```Python hl_lines="25-28" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +## ✍ 🏷 + +✍ Pydantic 🏷: + +* 🗒 ✍ (`NoteIn`). +* 🗒 📨 (`Note`). + +```Python hl_lines="31-33 36-39" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +🏗 👫 Pydantic 🏷, 🔢 💽 🔜 ✔, 🎻 (🗜), & ✍ (📄). + +, 👆 🔜 💪 👀 ⚫️ 🌐 🎓 🛠️ 🩺. + +## 🔗 & 🔌 + +* ✍ 👆 `FastAPI` 🈸. +* ✍ 🎉 🐕‍🦺 🔗 & 🔌 ⚪️➡️ 💽. + +```Python hl_lines="42 45-47 50-52" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +## ✍ 🗒 + +✍ *➡ 🛠️ 🔢* ✍ 🗒: + +```Python hl_lines="55-58" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! Note + 👀 👈 👥 🔗 ⏮️ 💽 ⚙️ `await`, *➡ 🛠️ 🔢* 📣 ⏮️ `async`. + +### 👀 `response_model=List[Note]` + +⚫️ ⚙️ `typing.List`. + +👈 📄 (& ✔, 🎻, ⛽) 🔢 💽, `list` `Note`Ⓜ. + +## ✍ 🗒 + +✍ *➡ 🛠️ 🔢* ✍ 🗒: + +```Python hl_lines="61-65" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! Note + 👀 👈 👥 🔗 ⏮️ 💽 ⚙️ `await`, *➡ 🛠️ 🔢* 📣 ⏮️ `async`. + +### 🔃 `{**note.dict(), "id": last_record_id}` + +`note` Pydantic `Note` 🎚. + +`note.dict()` 📨 `dict` ⏮️ 🚮 💽, 🕳 💖: + +```Python +{ + "text": "Some note", + "completed": False, +} +``` + +✋️ ⚫️ 🚫 ✔️ `id` 🏑. + +👥 ✍ 🆕 `dict`, 👈 🔌 🔑-💲 👫 ⚪️➡️ `note.dict()` ⏮️: + +```Python +{**note.dict()} +``` + +`**note.dict()` "unpacks" the key value pairs directly, so, `{**note.dict()}` would be, more or less, a copy of `note.dict()`. + +& ⤴️, 👥 ↔ 👈 📁 `dict`, ❎ ➕1️⃣ 🔑-💲 👫: `"id": last_record_id`: + +```Python +{**note.dict(), "id": last_record_id} +``` + +, 🏁 🏁 📨 🔜 🕳 💖: + +```Python +{ + "id": 1, + "text": "Some note", + "completed": False, +} +``` + +## ✅ ⚫️ + +👆 💪 📁 👉 📟, & 👀 🩺 http://127.0.0.1:8000/docs. + +📤 👆 💪 👀 🌐 👆 🛠️ 📄 & 🔗 ⏮️ ⚫️: + + + +## 🌅 ℹ + +👆 💪 ✍ 🌅 🔃 `encode/databases` 🚮 📂 📃. diff --git a/docs/em/docs/advanced/async-tests.md b/docs/em/docs/advanced/async-tests.md new file mode 100644 index 0000000000..df94c6ce7c --- /dev/null +++ b/docs/em/docs/advanced/async-tests.md @@ -0,0 +1,92 @@ +# 🔁 💯 + +👆 ✔️ ⏪ 👀 ❔ 💯 👆 **FastAPI** 🈸 ⚙️ 🚚 `TestClient`. 🆙 🔜, 👆 ✔️ 🕴 👀 ❔ ✍ 🔁 💯, 🍵 ⚙️ `async` 🔢. + +➖ 💪 ⚙️ 🔁 🔢 👆 💯 💪 ⚠, 🖼, 🕐❔ 👆 🔬 👆 💽 🔁. 🌈 👆 💚 💯 📨 📨 👆 FastAPI 🈸 & ⤴️ ✔ 👈 👆 👩‍💻 ⏪ ✍ ☑ 💽 💽, ⏪ ⚙️ 🔁 💽 🗃. + +➡️ 👀 ❔ 👥 💪 ⚒ 👈 👷. + +## pytest.mark.anyio + +🚥 👥 💚 🤙 🔁 🔢 👆 💯, 👆 💯 🔢 ✔️ 🔁. AnyIO 🚚 👌 📁 👉, 👈 ✔ 👥 ✔ 👈 💯 🔢 🤙 🔁. + +## 🇸🇲 + +🚥 👆 **FastAPI** 🈸 ⚙️ 😐 `def` 🔢 ↩️ `async def`, ⚫️ `async` 🈸 🔘. + +`TestClient` 🔨 🎱 🔘 🤙 🔁 FastAPI 🈸 👆 😐 `def` 💯 🔢, ⚙️ 🐩 ✳. ✋️ 👈 🎱 🚫 👷 🚫🔜 🕐❔ 👥 ⚙️ ⚫️ 🔘 🔁 🔢. 🏃 👆 💯 🔁, 👥 💪 🙅‍♂ 📏 ⚙️ `TestClient` 🔘 👆 💯 🔢. + +`TestClient` ⚓️ 🔛 🇸🇲, & ↩️, 👥 💪 ⚙️ ⚫️ 🔗 💯 🛠️. + +## 🖼 + +🙅 🖼, ➡️ 🤔 📁 📊 🎏 1️⃣ 🔬 [🦏 🈸](../tutorial/bigger-applications.md){.internal-link target=_blank} & [🔬](../tutorial/testing.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +📁 `main.py` 🔜 ✔️: + +```Python +{!../../../docs_src/async_tests/main.py!} +``` + +📁 `test_main.py` 🔜 ✔️ 💯 `main.py`, ⚫️ 💪 👀 💖 👉 🔜: + +```Python +{!../../../docs_src/async_tests/test_main.py!} +``` + +## 🏃 ⚫️ + +👆 💪 🏃 👆 💯 🐌 📨: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## ℹ + +📑 `@pytest.mark.anyio` 💬 ✳ 👈 👉 💯 🔢 🔜 🤙 🔁: + +```Python hl_lines="7" +{!../../../docs_src/async_tests/test_main.py!} +``` + +!!! tip + 🗒 👈 💯 🔢 🔜 `async def` ↩️ `def` ⏭ 🕐❔ ⚙️ `TestClient`. + +⤴️ 👥 💪 ✍ `AsyncClient` ⏮️ 📱, & 📨 🔁 📨 ⚫️, ⚙️ `await`. + +```Python hl_lines="9-10" +{!../../../docs_src/async_tests/test_main.py!} +``` + +👉 🌓: + +```Python +response = client.get('/') +``` + +...👈 👥 ⚙️ ⚒ 👆 📨 ⏮️ `TestClient`. + +!!! tip + 🗒 👈 👥 ⚙️ 🔁/⌛ ⏮️ 🆕 `AsyncClient` - 📨 🔁. + +## 🎏 🔁 🔢 🤙 + +🔬 🔢 🔜 🔁, 👆 💪 🔜 🤙 (& `await`) 🎏 `async` 🔢 ↖️ ⚪️➡️ 📨 📨 👆 FastAPI 🈸 👆 💯, ⚫️❔ 👆 🔜 🤙 👫 🙆 🙆 👆 📟. + +!!! tip + 🚥 👆 ⚔ `RuntimeError: Task attached to a different loop` 🕐❔ 🛠️ 🔁 🔢 🤙 👆 💯 (✅ 🕐❔ ⚙️ ✳ MotorClient) 💭 🔗 🎚 👈 💪 🎉 ➰ 🕴 🏞 🔁 🔢, ✅ `'@app.on_event("startup")` ⏲. diff --git a/docs/em/docs/advanced/behind-a-proxy.md b/docs/em/docs/advanced/behind-a-proxy.md new file mode 100644 index 0000000000..12afe638c6 --- /dev/null +++ b/docs/em/docs/advanced/behind-a-proxy.md @@ -0,0 +1,346 @@ +# ⛅ 🗳 + +⚠, 👆 5️⃣📆 💪 ⚙️ **🗳** 💽 💖 Traefik ⚖️ 👌 ⏮️ 📳 👈 🚮 ➕ ➡ 🔡 👈 🚫 👀 👆 🈸. + +👫 💼 👆 💪 ⚙️ `root_path` 🔗 👆 🈸. + +`root_path` 🛠️ 🚚 🔫 🔧 (👈 FastAPI 🏗 🔛, 🔘 💃). + +`root_path` ⚙️ 🍵 👫 🎯 💼. + +& ⚫️ ⚙️ 🔘 🕐❔ 🗜 🎧-🈸. + +## 🗳 ⏮️ 🎞 ➡ 🔡 + +✔️ 🗳 ⏮️ 🎞 ➡ 🔡, 👉 💼, ⛓ 👈 👆 💪 📣 ➡ `/app` 👆 📟, ✋️ ⤴️, 👆 🚮 🧽 🔛 🔝 (🗳) 👈 🔜 🚮 👆 **FastAPI** 🈸 🔽 ➡ 💖 `/api/v1`. + +👉 💼, ⏮️ ➡ `/app` 🔜 🤙 🍦 `/api/v1/app`. + +✋️ 🌐 👆 📟 ✍ 🤔 📤 `/app`. + +& 🗳 🔜 **"❎"** **➡ 🔡** 🔛 ✈ ⏭ 📶 📨 Uvicorn, 🚧 👆 🈸 🤔 👈 ⚫️ 🍦 `/app`, 👈 👆 🚫 ✔️ ℹ 🌐 👆 📟 🔌 🔡 `/api/v1`. + +🆙 📥, 🌐 🔜 👷 🛎. + +✋️ ⤴️, 🕐❔ 👆 📂 🛠️ 🩺 🎚 (🕸), ⚫️ 🔜 ⌛ 🤚 🗄 🔗 `/openapi.json`, ↩️ `/api/v1/openapi.json`. + +, 🕸 (👈 🏃 🖥) 🔜 🔄 🏆 `/openapi.json` & 🚫🔜 💪 🤚 🗄 🔗. + +↩️ 👥 ✔️ 🗳 ⏮️ ➡ 🔡 `/api/v1` 👆 📱, 🕸 💪 ☕ 🗄 🔗 `/api/v1/openapi.json`. + +```mermaid +graph LR + +browser("Browser") +proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] +server["Server on http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +!!! tip + 📢 `0.0.0.0` 🛎 ⚙️ ⛓ 👈 📋 👂 🔛 🌐 📢 💪 👈 🎰/💽. + +🩺 🎚 🔜 💪 🗄 🔗 📣 👈 👉 🛠️ `server` 🔎 `/api/v1` (⛅ 🗳). 🖼: + +```JSON hl_lines="4-8" +{ + "openapi": "3.0.2", + // More stuff here + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // More stuff here + } +} +``` + +👉 🖼, "🗳" 💪 🕳 💖 **Traefik**. & 💽 🔜 🕳 💖 **Uvicorn**, 🏃‍♂ 👆 FastAPI 🈸. + +### 🚚 `root_path` + +🏆 👉, 👆 💪 ⚙️ 📋 ⏸ 🎛 `--root-path` 💖: + +
+ +```console +$ uvicorn main:app --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +🚥 👆 ⚙️ Hypercorn, ⚫️ ✔️ 🎛 `--root-path`. + +!!! note "📡 ℹ" + 🔫 🔧 🔬 `root_path` 👉 ⚙️ 💼. + + & `--root-path` 📋 ⏸ 🎛 🚚 👈 `root_path`. + +### ✅ ⏮️ `root_path` + +👆 💪 🤚 ⏮️ `root_path` ⚙️ 👆 🈸 🔠 📨, ⚫️ 🍕 `scope` 📖 (👈 🍕 🔫 🔌). + +📥 👥 ✅ ⚫️ 📧 🎦 🎯. + +```Python hl_lines="8" +{!../../../docs_src/behind_a_proxy/tutorial001.py!} +``` + +⤴️, 🚥 👆 ▶️ Uvicorn ⏮️: + +
+ +```console +$ uvicorn main:app --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +📨 🔜 🕳 💖: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### ⚒ `root_path` FastAPI 📱 + +👐, 🚥 👆 🚫 ✔️ 🌌 🚚 📋 ⏸ 🎛 💖 `--root-path` ⚖️ 🌓, 👆 💪 ⚒ `root_path` 🔢 🕐❔ 🏗 👆 FastAPI 📱: + +```Python hl_lines="3" +{!../../../docs_src/behind_a_proxy/tutorial002.py!} +``` + +🚶‍♀️ `root_path` `FastAPI` 🔜 🌓 🚶‍♀️ `--root-path` 📋 ⏸ 🎛 Uvicorn ⚖️ Hypercorn. + +### 🔃 `root_path` + +✔️ 🤯 👈 💽 (Uvicorn) 🏆 🚫 ⚙️ 👈 `root_path` 🕳 🙆 🌘 🚶‍♀️ ⚫️ 📱. + +✋️ 🚥 👆 🚶 ⏮️ 👆 🖥 http://127.0.0.1:8000/app 👆 🔜 👀 😐 📨: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +, ⚫️ 🏆 🚫 ⌛ 🔐 `http://127.0.0.1:8000/api/v1/app`. + +Uvicorn 🔜 ⌛ 🗳 🔐 Uvicorn `http://127.0.0.1:8000/app`, & ⤴️ ⚫️ 🔜 🗳 🎯 🚮 ➕ `/api/v1` 🔡 🔛 🔝. + +## 🔃 🗳 ⏮️ 🎞 ➡ 🔡 + +✔️ 🤯 👈 🗳 ⏮️ 🎞 ➡ 🔡 🕴 1️⃣ 🌌 🔗 ⚫️. + +🎲 📚 💼 🔢 🔜 👈 🗳 🚫 ✔️ 🏚 ➡ 🔡. + +💼 💖 👈 (🍵 🎞 ➡ 🔡), 🗳 🔜 👂 🔛 🕳 💖 `https://myawesomeapp.com`, & ⤴️ 🚥 🖥 🚶 `https://myawesomeapp.com/api/v1/app` & 👆 💽 (✅ Uvicorn) 👂 🔛 `http://127.0.0.1:8000` 🗳 (🍵 🎞 ➡ 🔡) 🔜 🔐 Uvicorn 🎏 ➡: `http://127.0.0.1:8000/api/v1/app`. + +## 🔬 🌐 ⏮️ Traefik + +👆 💪 💪 🏃 🥼 🌐 ⏮️ 🎞 ➡ 🔡 ⚙️ Traefik. + +⏬ Traefik, ⚫️ 👁 💱, 👆 💪 ⚗ 🗜 📁 & 🏃 ⚫️ 🔗 ⚪️➡️ 📶. + +⤴️ ✍ 📁 `traefik.toml` ⏮️: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +👉 💬 Traefik 👂 🔛 ⛴ 9️⃣9️⃣9️⃣9️⃣ & ⚙️ ➕1️⃣ 📁 `routes.toml`. + +!!! tip + 👥 ⚙️ ⛴ 9️⃣9️⃣9️⃣9️⃣ ↩️ 🐩 🇺🇸🔍 ⛴ 8️⃣0️⃣ 👈 👆 🚫 ✔️ 🏃 ⚫️ ⏮️ 📡 (`sudo`) 😌. + +🔜 ✍ 👈 🎏 📁 `routes.toml`: + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +👉 📁 🔗 Traefik ⚙️ ➡ 🔡 `/api/v1`. + +& ⤴️ ⚫️ 🔜 ❎ 🚮 📨 👆 Uvicorn 🏃‍♂ 🔛 `http://127.0.0.1:8000`. + +🔜 ▶️ Traefik: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +& 🔜 ▶️ 👆 📱 ⏮️ Uvicorn, ⚙️ `--root-path` 🎛: + +
+ +```console +$ uvicorn main:app --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### ✅ 📨 + +🔜, 🚥 👆 🚶 📛 ⏮️ ⛴ Uvicorn: http://127.0.0.1:8000/app, 👆 🔜 👀 😐 📨: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +!!! tip + 👀 👈 ✋️ 👆 🔐 ⚫️ `http://127.0.0.1:8000/app` ⚫️ 🎦 `root_path` `/api/v1`, ✊ ⚪️➡️ 🎛 `--root-path`. + +& 🔜 📂 📛 ⏮️ ⛴ Traefik, ✅ ➡ 🔡: http://127.0.0.1:9999/api/v1/app. + +👥 🤚 🎏 📨: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +✋️ 👉 🕰 📛 ⏮️ 🔡 ➡ 🚚 🗳: `/api/v1`. + +↗️, 💭 📥 👈 👱 🔜 🔐 📱 🔘 🗳, ⏬ ⏮️ ➡ 🔡 `/app/v1` "☑" 1️⃣. + +& ⏬ 🍵 ➡ 🔡 (`http://127.0.0.1:8000/app`), 🚚 Uvicorn 🔗, 🔜 🎯 _🗳_ (Traefik) 🔐 ⚫️. + +👈 🎦 ❔ 🗳 (Traefik) ⚙️ ➡ 🔡 & ❔ 💽 (Uvicorn) ⚙️ `root_path` ⚪️➡️ 🎛 `--root-path`. + +### ✅ 🩺 🎚 + +✋️ 📥 🎊 🍕. 👶 + +"🛂" 🌌 🔐 📱 🔜 🔘 🗳 ⏮️ ➡ 🔡 👈 👥 🔬. , 👥 🔜 ⌛, 🚥 👆 🔄 🩺 🎚 🍦 Uvicorn 🔗, 🍵 ➡ 🔡 📛, ⚫️ 🏆 🚫 👷, ↩️ ⚫️ ⌛ 🔐 🔘 🗳. + +👆 💪 ✅ ⚫️ http://127.0.0.1:8000/docs: + + + +✋️ 🚥 👥 🔐 🩺 🎚 "🛂" 📛 ⚙️ 🗳 ⏮️ ⛴ `9999`, `/api/v1/docs`, ⚫️ 👷 ☑ ❗ 👶 + +👆 💪 ✅ ⚫️ http://127.0.0.1:9999/api/v1/docs: + + + +▶️️ 👥 💚 ⚫️. 👶 👶 + +👉 ↩️ FastAPI ⚙️ 👉 `root_path` ✍ 🔢 `server` 🗄 ⏮️ 📛 🚚 `root_path`. + +## 🌖 💽 + +!!! warning + 👉 🌅 🏧 ⚙️ 💼. 💭 🆓 🚶 ⚫️. + +🔢, **FastAPI** 🔜 ✍ `server` 🗄 🔗 ⏮️ 📛 `root_path`. + +✋️ 👆 💪 🚚 🎏 🎛 `servers`, 🖼 🚥 👆 💚 *🎏* 🩺 🎚 🔗 ⏮️ 🏗 & 🏭 🌐. + +🚥 👆 🚶‍♀️ 🛃 📇 `servers` & 📤 `root_path` (↩️ 👆 🛠️ 👨‍❤‍👨 ⛅ 🗳), **FastAPI** 🔜 📩 "💽" ⏮️ 👉 `root_path` ▶️ 📇. + +🖼: + +```Python hl_lines="4-7" +{!../../../docs_src/behind_a_proxy/tutorial003.py!} +``` + +🔜 🏗 🗄 🔗 💖: + +```JSON hl_lines="5-7" +{ + "openapi": "3.0.2", + // More stuff here + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // More stuff here + } +} +``` + +!!! tip + 👀 🚘-🏗 💽 ⏮️ `url` 💲 `/api/v1`, ✊ ⚪️➡️ `root_path`. + +🩺 🎚 http://127.0.0.1:9999/api/v1/docs ⚫️ 🔜 👀 💖: + + + +!!! tip + 🩺 🎚 🔜 🔗 ⏮️ 💽 👈 👆 🖊. + +### ❎ 🏧 💽 ⚪️➡️ `root_path` + +🚥 👆 🚫 💚 **FastAPI** 🔌 🏧 💽 ⚙️ `root_path`, 👆 💪 ⚙️ 🔢 `root_path_in_servers=False`: + +```Python hl_lines="9" +{!../../../docs_src/behind_a_proxy/tutorial004.py!} +``` + +& ⤴️ ⚫️ 🏆 🚫 🔌 ⚫️ 🗄 🔗. + +## 🗜 🎧-🈸 + +🚥 👆 💪 🗻 🎧-🈸 (🔬 [🎧 🈸 - 🗻](./sub-applications.md){.internal-link target=_blank}) ⏪ ⚙️ 🗳 ⏮️ `root_path`, 👆 💪 ⚫️ 🛎, 👆 🔜 ⌛. + +FastAPI 🔜 🔘 ⚙️ `root_path` 🎆, ⚫️ 🔜 👷. 👶 diff --git a/docs/em/docs/advanced/conditional-openapi.md b/docs/em/docs/advanced/conditional-openapi.md new file mode 100644 index 0000000000..a17ba4eec5 --- /dev/null +++ b/docs/em/docs/advanced/conditional-openapi.md @@ -0,0 +1,58 @@ +# 🎲 🗄 + +🚥 👆 💪, 👆 💪 ⚙️ ⚒ & 🌐 🔢 🔗 🗄 ✔ ⚓️ 🔛 🌐, & ❎ ⚫️ 🍕. + +## 🔃 💂‍♂, 🔗, & 🩺 + +🕵‍♂ 👆 🧾 👩‍💻 🔢 🏭 *🚫🔜 🚫* 🌌 🛡 👆 🛠️. + +👈 🚫 🚮 🙆 ➕ 💂‍♂ 👆 🛠️, *➡ 🛠️* 🔜 💪 🌐❔ 👫. + +🚥 📤 💂‍♂ ⚠ 👆 📟, ⚫️ 🔜 🔀. + +🕵‍♂ 🧾 ⚒ ⚫️ 🌅 ⚠ 🤔 ❔ 🔗 ⏮️ 👆 🛠️, & 💪 ⚒ ⚫️ 🌅 ⚠ 👆 ℹ ⚫️ 🏭. ⚫️ 💪 🤔 🎯 📨 💂‍♂ 🔘 🌌. + +🚥 👆 💚 🔐 👆 🛠️, 📤 📚 👍 👜 👆 💪, 🖼: + +* ⚒ 💭 👆 ✔️ 👍 🔬 Pydantic 🏷 👆 📨 💪 & 📨. +* 🔗 🙆 ✔ ✔ & 🔑 ⚙️ 🔗. +* 🙅 🏪 🔢 🔐, 🕴 🔐#️⃣. +* 🛠️ & ⚙️ 👍-💭 🔐 🧰, 💖 🇸🇲 & 🥙 🤝, ♒️. +* 🚮 🌅 🧽 ✔ 🎛 ⏮️ Oauth2️⃣ ↔ 🌐❔ 💪. +* ...♒️. + +👐, 👆 5️⃣📆 ✔️ 📶 🎯 ⚙️ 💼 🌐❔ 👆 🤙 💪 ❎ 🛠️ 🩺 🌐 (✅ 🏭) ⚖️ ⚓️ 🔛 📳 ⚪️➡️ 🌐 🔢. + +## 🎲 🗄 ⚪️➡️ ⚒ & 🇨🇻 { + +👆 💪 💪 ⚙️ 🎏 Pydantic ⚒ 🔗 👆 🏗 🗄 & 🩺 ⚜. + +🖼: + +```Python hl_lines="6 11" +{!../../../docs_src/conditional_openapi/tutorial001.py!} +``` + +📥 👥 📣 ⚒ `openapi_url` ⏮️ 🎏 🔢 `"/openapi.json"`. + +& ⤴️ 👥 ⚙️ ⚫️ 🕐❔ 🏗 `FastAPI` 📱. + +⤴️ 👆 💪 ❎ 🗄 (✅ 🎚 🩺) ⚒ 🌐 🔢 `OPENAPI_URL` 🛁 🎻, 💖: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +⤴️ 🚥 👆 🚶 📛 `/openapi.json`, `/docs`, ⚖️ `/redoc` 👆 🔜 🤚 `404 Not Found` ❌ 💖: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/em/docs/advanced/custom-request-and-route.md b/docs/em/docs/advanced/custom-request-and-route.md new file mode 100644 index 0000000000..d6fafa2ea6 --- /dev/null +++ b/docs/em/docs/advanced/custom-request-and-route.md @@ -0,0 +1,109 @@ +# 🛃 📨 & APIRoute 🎓 + +💼, 👆 5️⃣📆 💚 🔐 ⚛ ⚙️ `Request` & `APIRoute` 🎓. + +🎯, 👉 5️⃣📆 👍 🎛 ⚛ 🛠️. + +🖼, 🚥 👆 💚 ✍ ⚖️ 🔬 📨 💪 ⏭ ⚫️ 🛠️ 👆 🈸. + +!!! danger + 👉 "🏧" ⚒. + + 🚥 👆 ▶️ ⏮️ **FastAPI** 👆 💪 💚 🚶 👉 📄. + +## ⚙️ 💼 + +⚙️ 💼 🔌: + +* 🏭 🚫-🎻 📨 💪 🎻 (✅ `msgpack`). +* 🗜 🗜-🗜 📨 💪. +* 🔁 🚨 🌐 📨 💪. + +## 🚚 🛃 📨 💪 🔢 + +➡️ 👀 ❔ ⚒ ⚙️ 🛃 `Request` 🏿 🗜 🗜 📨. + +& `APIRoute` 🏿 ⚙️ 👈 🛃 📨 🎓. + +### ✍ 🛃 `GzipRequest` 🎓 + +!!! tip + 👉 🧸 🖼 🎦 ❔ ⚫️ 👷, 🚥 👆 💪 🗜 🐕‍🦺, 👆 💪 ⚙️ 🚚 [`GzipMiddleware`](./middleware.md#gzipmiddleware){.internal-link target=_blank}. + +🥇, 👥 ✍ `GzipRequest` 🎓, ❔ 🔜 📁 `Request.body()` 👩‍🔬 🗜 💪 🔍 ☑ 🎚. + +🚥 📤 🙅‍♂ `gzip` 🎚, ⚫️ 🔜 🚫 🔄 🗜 💪. + +👈 🌌, 🎏 🛣 🎓 💪 🍵 🗜 🗜 ⚖️ 🗜 📨. + +```Python hl_lines="8-15" +{!../../../docs_src/custom_request_and_route/tutorial001.py!} +``` + +### ✍ 🛃 `GzipRoute` 🎓 + +⏭, 👥 ✍ 🛃 🏿 `fastapi.routing.APIRoute` 👈 🔜 ⚒ ⚙️ `GzipRequest`. + +👉 🕰, ⚫️ 🔜 📁 👩‍🔬 `APIRoute.get_route_handler()`. + +👉 👩‍🔬 📨 🔢. & 👈 🔢 ⚫️❔ 🔜 📨 📨 & 📨 📨. + +📥 👥 ⚙️ ⚫️ ✍ `GzipRequest` ⚪️➡️ ⏮️ 📨. + +```Python hl_lines="18-26" +{!../../../docs_src/custom_request_and_route/tutorial001.py!} +``` + +!!! note "📡 ℹ" + `Request` ✔️ `request.scope` 🔢, 👈 🐍 `dict` ⚗ 🗃 🔗 📨. + + `Request` ✔️ `request.receive`, 👈 🔢 "📨" 💪 📨. + + `scope` `dict` & `receive` 🔢 👯‍♂️ 🍕 🔫 🔧. + + & 👈 2️⃣ 👜, `scope` & `receive`, ⚫️❔ 💪 ✍ 🆕 `Request` 👐. + + 💡 🌅 🔃 `Request` ✅ 💃 🩺 🔃 📨. + +🕴 👜 🔢 📨 `GzipRequest.get_route_handler` 🔨 🎏 🗜 `Request` `GzipRequest`. + +🔨 👉, 👆 `GzipRequest` 🔜 ✊ 💅 🗜 📊 (🚥 💪) ⏭ 🚶‍♀️ ⚫️ 👆 *➡ 🛠️*. + +⏮️ 👈, 🌐 🏭 ⚛ 🎏. + +✋️ ↩️ 👆 🔀 `GzipRequest.body`, 📨 💪 🔜 🔁 🗜 🕐❔ ⚫️ 📐 **FastAPI** 🕐❔ 💪. + +## 🔐 📨 💪 ⚠ 🐕‍🦺 + +!!! tip + ❎ 👉 🎏 ⚠, ⚫️ 🎲 📚 ⏩ ⚙️ `body` 🛃 🐕‍🦺 `RequestValidationError` ([🚚 ❌](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + + ✋️ 👉 🖼 ☑ & ⚫️ 🎦 ❔ 🔗 ⏮️ 🔗 🦲. + +👥 💪 ⚙️ 👉 🎏 🎯 🔐 📨 💪 ⚠ 🐕‍🦺. + +🌐 👥 💪 🍵 📨 🔘 `try`/`except` 🍫: + +```Python hl_lines="13 15" +{!../../../docs_src/custom_request_and_route/tutorial002.py!} +``` + +🚥 ⚠ 📉, `Request` 👐 🔜 ↔, 👥 💪 ✍ & ⚒ ⚙️ 📨 💪 🕐❔ 🚚 ❌: + +```Python hl_lines="16-18" +{!../../../docs_src/custom_request_and_route/tutorial002.py!} +``` + +## 🛃 `APIRoute` 🎓 📻 + +👆 💪 ⚒ `route_class` 🔢 `APIRouter`: + +```Python hl_lines="26" +{!../../../docs_src/custom_request_and_route/tutorial003.py!} +``` + +👉 🖼, *➡ 🛠️* 🔽 `router` 🔜 ⚙️ 🛃 `TimedRoute` 🎓, & 🔜 ✔️ ➕ `X-Response-Time` 🎚 📨 ⏮️ 🕰 ⚫️ ✊ 🏗 📨: + +```Python hl_lines="13-20" +{!../../../docs_src/custom_request_and_route/tutorial003.py!} +``` diff --git a/docs/em/docs/advanced/custom-response.md b/docs/em/docs/advanced/custom-response.md new file mode 100644 index 0000000000..cf76c01d06 --- /dev/null +++ b/docs/em/docs/advanced/custom-response.md @@ -0,0 +1,300 @@ +# 🛃 📨 - 🕸, 🎏, 📁, 🎏 + +🔢, **FastAPI** 🔜 📨 📨 ⚙️ `JSONResponse`. + +👆 💪 🔐 ⚫️ 🛬 `Response` 🔗 👀 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}. + +✋️ 🚥 👆 📨 `Response` 🔗, 📊 🏆 🚫 🔁 🗜, & 🧾 🏆 🚫 🔁 🏗 (🖼, 🔌 🎯 "📻 🆎", 🇺🇸🔍 🎚 `Content-Type` 🍕 🏗 🗄). + +✋️ 👆 💪 📣 `Response` 👈 👆 💚 ⚙️, *➡ 🛠️ 👨‍🎨*. + +🎚 👈 👆 📨 ⚪️➡️ 👆 *➡ 🛠️ 🔢* 🔜 🚮 🔘 👈 `Response`. + +& 🚥 👈 `Response` ✔️ 🎻 📻 🆎 (`application/json`), 💖 💼 ⏮️ `JSONResponse` & `UJSONResponse`, 💽 👆 📨 🔜 🔁 🗜 (& ⛽) ⏮️ 🙆 Pydantic `response_model` 👈 👆 📣 *➡ 🛠️ 👨‍🎨*. + +!!! note + 🚥 👆 ⚙️ 📨 🎓 ⏮️ 🙅‍♂ 📻 🆎, FastAPI 🔜 ⌛ 👆 📨 ✔️ 🙅‍♂ 🎚, ⚫️ 🔜 🚫 📄 📨 📁 🚮 🏗 🗄 🩺. + +## ⚙️ `ORJSONResponse` + +🖼, 🚥 👆 ✊ 🎭, 👆 💪 ❎ & ⚙️ `orjson` & ⚒ 📨 `ORJSONResponse`. + +🗄 `Response` 🎓 (🎧-🎓) 👆 💚 ⚙️ & 📣 ⚫️ *➡ 🛠️ 👨‍🎨*. + +⭕ 📨, 📨 `Response` 🔗 🌅 ⏩ 🌘 🛬 📖. + +👉 ↩️ 🔢, FastAPI 🔜 ✔ 🔠 🏬 🔘 & ⚒ 💭 ⚫️ 🎻 ⏮️ 🎻, ⚙️ 🎏 [🎻 🔗 🔢](../tutorial/encoder.md){.internal-link target=_blank} 🔬 🔰. 👉 ⚫️❔ ✔ 👆 📨 **❌ 🎚**, 🖼 💽 🏷. + +✋️ 🚥 👆 🎯 👈 🎚 👈 👆 🛬 **🎻 ⏮️ 🎻**, 👆 💪 🚶‍♀️ ⚫️ 🔗 📨 🎓 & ❎ ➕ 🌥 👈 FastAPI 🔜 ✔️ 🚶‍♀️ 👆 📨 🎚 🔘 `jsonable_encoder` ⏭ 🚶‍♀️ ⚫️ 📨 🎓. + +```Python hl_lines="2 7" +{!../../../docs_src/custom_response/tutorial001b.py!} +``` + +!!! info + 🔢 `response_class` 🔜 ⚙️ 🔬 "📻 🆎" 📨. + + 👉 💼, 🇺🇸🔍 🎚 `Content-Type` 🔜 ⚒ `application/json`. + + & ⚫️ 🔜 📄 ✅ 🗄. + +!!! tip + `ORJSONResponse` ⏳ 🕴 💪 FastAPI, 🚫 💃. + +## 🕸 📨 + +📨 📨 ⏮️ 🕸 🔗 ⚪️➡️ **FastAPI**, ⚙️ `HTMLResponse`. + +* 🗄 `HTMLResponse`. +* 🚶‍♀️ `HTMLResponse` 🔢 `response_class` 👆 *➡ 🛠️ 👨‍🎨*. + +```Python hl_lines="2 7" +{!../../../docs_src/custom_response/tutorial002.py!} +``` + +!!! info + 🔢 `response_class` 🔜 ⚙️ 🔬 "📻 🆎" 📨. + + 👉 💼, 🇺🇸🔍 🎚 `Content-Type` 🔜 ⚒ `text/html`. + + & ⚫️ 🔜 📄 ✅ 🗄. + +### 📨 `Response` + +👀 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}, 👆 💪 🔐 📨 🔗 👆 *➡ 🛠️*, 🛬 ⚫️. + +🎏 🖼 ⚪️➡️ 🔛, 🛬 `HTMLResponse`, 💪 👀 💖: + +```Python hl_lines="2 7 19" +{!../../../docs_src/custom_response/tutorial003.py!} +``` + +!!! warning + `Response` 📨 🔗 👆 *➡ 🛠️ 🔢* 🏆 🚫 📄 🗄 (🖼, `Content-Type` 🏆 🚫 📄) & 🏆 🚫 ⭐ 🏧 🎓 🩺. + +!!! info + ↗️, ☑ `Content-Type` 🎚, 👔 📟, ♒️, 🔜 👟 ⚪️➡️ `Response` 🎚 👆 📨. + +### 📄 🗄 & 🔐 `Response` + +🚥 👆 💚 🔐 📨 ⚪️➡️ 🔘 🔢 ✋️ 🎏 🕰 📄 "📻 🆎" 🗄, 👆 💪 ⚙️ `response_class` 🔢 & 📨 `Response` 🎚. + +`response_class` 🔜 ⤴️ ⚙️ 🕴 📄 🗄 *➡ 🛠️*, ✋️ 👆 `Response` 🔜 ⚙️. + +#### 📨 `HTMLResponse` 🔗 + +🖼, ⚫️ 💪 🕳 💖: + +```Python hl_lines="7 21 23" +{!../../../docs_src/custom_response/tutorial004.py!} +``` + +👉 🖼, 🔢 `generate_html_response()` ⏪ 🏗 & 📨 `Response` ↩️ 🛬 🕸 `str`. + +🛬 🏁 🤙 `generate_html_response()`, 👆 ⏪ 🛬 `Response` 👈 🔜 🔐 🔢 **FastAPI** 🎭. + +✋️ 👆 🚶‍♀️ `HTMLResponse` `response_class` 💁‍♂️, **FastAPI** 🔜 💭 ❔ 📄 ⚫️ 🗄 & 🎓 🩺 🕸 ⏮️ `text/html`: + + + +## 💪 📨 + +📥 💪 📨. + +✔️ 🤯 👈 👆 💪 ⚙️ `Response` 📨 🕳 🙆, ⚖️ ✍ 🛃 🎧-🎓. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.responses import HTMLResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. + +### `Response` + +👑 `Response` 🎓, 🌐 🎏 📨 😖 ⚪️➡️ ⚫️. + +👆 💪 📨 ⚫️ 🔗. + +⚫️ 🚫 📄 🔢: + +* `content` - `str` ⚖️ `bytes`. +* `status_code` - `int` 🇺🇸🔍 👔 📟. +* `headers` - `dict` 🎻. +* `media_type` - `str` 🤝 📻 🆎. 🤶 Ⓜ. `"text/html"`. + +FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 🎚, ⚓️ 🔛 = & 🔁 = ✍ 🆎. + +```Python hl_lines="1 18" +{!../../../docs_src/response_directly/tutorial002.py!} +``` + +### `HTMLResponse` + +✊ ✍ ⚖️ 🔢 & 📨 🕸 📨, 👆 ✍ 🔛. + +### `PlainTextResponse` + +✊ ✍ ⚖️ 🔢 & 📨 ✅ ✍ 📨. + +```Python hl_lines="2 7 9" +{!../../../docs_src/custom_response/tutorial005.py!} +``` + +### `JSONResponse` + +✊ 💽 & 📨 `application/json` 🗜 📨. + +👉 🔢 📨 ⚙️ **FastAPI**, 👆 ✍ 🔛. + +### `ORJSONResponse` + +⏩ 🎛 🎻 📨 ⚙️ `orjson`, 👆 ✍ 🔛. + +### `UJSONResponse` + +🎛 🎻 📨 ⚙️ `ujson`. + +!!! warning + `ujson` 🌘 💛 🌘 🐍 🏗-🛠️ ❔ ⚫️ 🍵 📐-💼. + +```Python hl_lines="2 7" +{!../../../docs_src/custom_response/tutorial001.py!} +``` + +!!! tip + ⚫️ 💪 👈 `ORJSONResponse` 💪 ⏩ 🎛. + +### `RedirectResponse` + +📨 🇺🇸🔍 ❎. ⚙️ 3️⃣0️⃣7️⃣ 👔 📟 (🍕 ❎) 🔢. + +👆 💪 📨 `RedirectResponse` 🔗: + +```Python hl_lines="2 9" +{!../../../docs_src/custom_response/tutorial006.py!} +``` + +--- + +⚖️ 👆 💪 ⚙️ ⚫️ `response_class` 🔢: + + +```Python hl_lines="2 7 9" +{!../../../docs_src/custom_response/tutorial006b.py!} +``` + +🚥 👆 👈, ⤴️ 👆 💪 📨 📛 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢. + +👉 💼, `status_code` ⚙️ 🔜 🔢 1️⃣ `RedirectResponse`, ❔ `307`. + +--- + +👆 💪 ⚙️ `status_code` 🔢 🌀 ⏮️ `response_class` 🔢: + +```Python hl_lines="2 7 9" +{!../../../docs_src/custom_response/tutorial006c.py!} +``` + +### `StreamingResponse` + +✊ 🔁 🚂 ⚖️ 😐 🚂/🎻 & 🎏 📨 💪. + +```Python hl_lines="2 14" +{!../../../docs_src/custom_response/tutorial007.py!} +``` + +#### ⚙️ `StreamingResponse` ⏮️ 📁-💖 🎚 + +🚥 👆 ✔️ 📁-💖 🎚 (✅ 🎚 📨 `open()`), 👆 💪 ✍ 🚂 🔢 🔁 🤭 👈 📁-💖 🎚. + +👈 🌌, 👆 🚫 ✔️ ✍ ⚫️ 🌐 🥇 💾, & 👆 💪 🚶‍♀️ 👈 🚂 🔢 `StreamingResponse`, & 📨 ⚫️. + +👉 🔌 📚 🗃 🔗 ⏮️ ☁ 💾, 📹 🏭, & 🎏. + +```{ .python .annotate hl_lines="2 10-12 14" } +{!../../../docs_src/custom_response/tutorial008.py!} +``` + +1️⃣. 👉 🚂 🔢. ⚫️ "🚂 🔢" ↩️ ⚫️ 🔌 `yield` 📄 🔘. +2️⃣. ⚙️ `with` 🍫, 👥 ⚒ 💭 👈 📁-💖 🎚 📪 ⏮️ 🚂 🔢 🔨. , ⏮️ ⚫️ 🏁 📨 📨. +3️⃣. 👉 `yield from` 💬 🔢 🔁 🤭 👈 👜 🌟 `file_like`. & ⤴️, 🔠 🍕 🔁, 🌾 👈 🍕 👟 ⚪️➡️ 👉 🚂 🔢. + + , ⚫️ 🚂 🔢 👈 📨 "🏭" 👷 🕳 🙆 🔘. + + 🔨 ⚫️ 👉 🌌, 👥 💪 🚮 ⚫️ `with` 🍫, & 👈 🌌, 🚚 👈 ⚫️ 📪 ⏮️ 🏁. + +!!! tip + 👀 👈 📥 👥 ⚙️ 🐩 `open()` 👈 🚫 🐕‍🦺 `async` & `await`, 👥 📣 ➡ 🛠️ ⏮️ 😐 `def`. + +### `FileResponse` + +🔁 🎏 📁 📨. + +✊ 🎏 ⚒ ❌ 🔗 🌘 🎏 📨 🆎: + +* `path` - 📁 📁 🎏. +* `headers` - 🙆 🛃 🎚 🔌, 📖. +* `media_type` - 🎻 🤝 📻 🆎. 🚥 🔢, 📁 ⚖️ ➡ 🔜 ⚙️ 🔑 📻 🆎. +* `filename` - 🚥 ⚒, 👉 🔜 🔌 📨 `Content-Disposition`. + +📁 📨 🔜 🔌 ☑ `Content-Length`, `Last-Modified` & `ETag` 🎚. + +```Python hl_lines="2 10" +{!../../../docs_src/custom_response/tutorial009.py!} +``` + +👆 💪 ⚙️ `response_class` 🔢: + +```Python hl_lines="2 8 10" +{!../../../docs_src/custom_response/tutorial009b.py!} +``` + +👉 💼, 👆 💪 📨 📁 ➡ 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢. + +## 🛃 📨 🎓 + +👆 💪 ✍ 👆 👍 🛃 📨 🎓, 😖 ⚪️➡️ `Response` & ⚙️ ⚫️. + +🖼, ➡️ 💬 👈 👆 💚 ⚙️ `orjson`, ✋️ ⏮️ 🛃 ⚒ 🚫 ⚙️ 🔌 `ORJSONResponse` 🎓. + +➡️ 💬 👆 💚 ⚫️ 📨 🔂 & 📁 🎻, 👆 💚 ⚙️ Orjson 🎛 `orjson.OPT_INDENT_2`. + +👆 💪 ✍ `CustomORJSONResponse`. 👑 👜 👆 ✔️ ✍ `Response.render(content)` 👩‍🔬 👈 📨 🎚 `bytes`: + +```Python hl_lines="9-14 17" +{!../../../docs_src/custom_response/tutorial009c.py!} +``` + +🔜 ↩️ 🛬: + +```json +{"message": "Hello World"} +``` + +...👉 📨 🔜 📨: + +```json +{ + "message": "Hello World" +} +``` + +↗️, 👆 🔜 🎲 🔎 🌅 👍 🌌 ✊ 📈 👉 🌘 ❕ 🎻. 👶 + +## 🔢 📨 🎓 + +🕐❔ 🏗 **FastAPI** 🎓 👐 ⚖️ `APIRouter` 👆 💪 ✔ ❔ 📨 🎓 ⚙️ 🔢. + +🔢 👈 🔬 👉 `default_response_class`. + +🖼 🔛, **FastAPI** 🔜 ⚙️ `ORJSONResponse` 🔢, 🌐 *➡ 🛠️*, ↩️ `JSONResponse`. + +```Python hl_lines="2 4" +{!../../../docs_src/custom_response/tutorial010.py!} +``` + +!!! tip + 👆 💪 🔐 `response_class` *➡ 🛠️* ⏭. + +## 🌖 🧾 + +👆 💪 📣 📻 🆎 & 📚 🎏 ℹ 🗄 ⚙️ `responses`: [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/dataclasses.md b/docs/em/docs/advanced/dataclasses.md new file mode 100644 index 0000000000..a4c2871062 --- /dev/null +++ b/docs/em/docs/advanced/dataclasses.md @@ -0,0 +1,98 @@ +# ⚙️ 🎻 + +FastAPI 🏗 🔛 🔝 **Pydantic**, & 👤 ✔️ 🌏 👆 ❔ ⚙️ Pydantic 🏷 📣 📨 & 📨. + +✋️ FastAPI 🐕‍🦺 ⚙️ `dataclasses` 🎏 🌌: + +```Python hl_lines="1 7-12 19-20" +{!../../../docs_src/dataclasses/tutorial001.py!} +``` + +👉 🐕‍🦺 👏 **Pydantic**, ⚫️ ✔️ 🔗 🐕‍🦺 `dataclasses`. + +, ⏮️ 📟 🔛 👈 🚫 ⚙️ Pydantic 🎯, FastAPI ⚙️ Pydantic 🗜 📚 🐩 🎻 Pydantic 👍 🍛 🎻. + +& ↗️, ⚫️ 🐕‍🦺 🎏: + +* 💽 🔬 +* 💽 🛠️ +* 💽 🧾, ♒️. + +👉 👷 🎏 🌌 ⏮️ Pydantic 🏷. & ⚫️ 🤙 🏆 🎏 🌌 🔘, ⚙️ Pydantic. + +!!! info + ✔️ 🤯 👈 🎻 💪 🚫 🌐 Pydantic 🏷 💪. + + , 👆 5️⃣📆 💪 ⚙️ Pydantic 🏷. + + ✋️ 🚥 👆 ✔️ 📚 🎻 🤥 🤭, 👉 👌 🎱 ⚙️ 👫 🏋️ 🕸 🛠️ ⚙️ FastAPI. 👶 + +## 🎻 `response_model` + +👆 💪 ⚙️ `dataclasses` `response_model` 🔢: + +```Python hl_lines="1 7-13 19" +{!../../../docs_src/dataclasses/tutorial002.py!} +``` + +🎻 🔜 🔁 🗜 Pydantic 🎻. + +👉 🌌, 🚮 🔗 🔜 🎦 🆙 🛠️ 🩺 👩‍💻 🔢: + + + +## 🎻 🔁 📊 📊 + +👆 💪 🌀 `dataclasses` ⏮️ 🎏 🆎 ✍ ⚒ 🐦 📊 📊. + +💼, 👆 💪 ✔️ ⚙️ Pydantic ⏬ `dataclasses`. 🖼, 🚥 👆 ✔️ ❌ ⏮️ 🔁 🏗 🛠️ 🧾. + +👈 💼, 👆 💪 🎯 💱 🐩 `dataclasses` ⏮️ `pydantic.dataclasses`, ❔ 💧-♻: + +```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } +{!../../../docs_src/dataclasses/tutorial003.py!} +``` + +1️⃣. 👥 🗄 `field` ⚪️➡️ 🐩 `dataclasses`. + +2️⃣. `pydantic.dataclasses` 💧-♻ `dataclasses`. + +3️⃣. `Author` 🎻 🔌 📇 `Item` 🎻. + +4️⃣. `Author` 🎻 ⚙️ `response_model` 🔢. + +5️⃣. 👆 💪 ⚙️ 🎏 🐩 🆎 ✍ ⏮️ 🎻 📨 💪. + + 👉 💼, ⚫️ 📇 `Item` 🎻. + +6️⃣. 📥 👥 🛬 📖 👈 🔌 `items` ❔ 📇 🎻. + + FastAPI 🎯 💽 🎻. + +7️⃣. 📥 `response_model` ⚙️ 🆎 ✍ 📇 `Author` 🎻. + + 🔄, 👆 💪 🌀 `dataclasses` ⏮️ 🐩 🆎 ✍. + +8️⃣. 👀 👈 👉 *➡ 🛠️ 🔢* ⚙️ 🥔 `def` ↩️ `async def`. + + 🕧, FastAPI 👆 💪 🌀 `def` & `async def` 💪. + + 🚥 👆 💪 ↗️ 🔃 🕐❔ ⚙️ ❔, ✅ 👅 📄 _"🏃 ❓" _ 🩺 🔃 `async` & `await`. + +9️⃣. 👉 *➡ 🛠️ 🔢* 🚫 🛬 🎻 (👐 ⚫️ 💪), ✋️ 📇 📖 ⏮️ 🔗 💽. + + FastAPI 🔜 ⚙️ `response_model` 🔢 (👈 🔌 🎻) 🗜 📨. + +👆 💪 🌀 `dataclasses` ⏮️ 🎏 🆎 ✍ 📚 🎏 🌀 📨 🏗 📊 📊. + +✅-📟 ✍ 💁‍♂ 🔛 👀 🌅 🎯 ℹ. + +## 💡 🌅 + +👆 💪 🌀 `dataclasses` ⏮️ 🎏 Pydantic 🏷, 😖 ⚪️➡️ 👫, 🔌 👫 👆 👍 🏷, ♒️. + +💡 🌅, ✅ Pydantic 🩺 🔃 🎻. + +## ⏬ + +👉 💪 ↩️ FastAPI ⏬ `0.67.0`. 👶 diff --git a/docs/em/docs/advanced/events.md b/docs/em/docs/advanced/events.md new file mode 100644 index 0000000000..671e81b186 --- /dev/null +++ b/docs/em/docs/advanced/events.md @@ -0,0 +1,160 @@ +# 🔆 🎉 + +👆 💪 🔬 ⚛ (📟) 👈 🔜 🛠️ ⏭ 🈸 **▶️ 🆙**. 👉 ⛓ 👈 👉 📟 🔜 🛠️ **🕐**, **⏭** 🈸 **▶️ 📨 📨**. + +🎏 🌌, 👆 💪 🔬 ⚛ (📟) 👈 🔜 🛠️ 🕐❔ 🈸 **🤫 🔽**. 👉 💼, 👉 📟 🔜 🛠️ **🕐**, **⏮️** ✔️ 🍵 🎲 **📚 📨**. + +↩️ 👉 📟 🛠️ ⏭ 🈸 **▶️** ✊ 📨, & ▶️️ ⏮️ ⚫️ **🏁** 🚚 📨, ⚫️ 📔 🎂 🈸 **🔆** (🔤 "🔆" 🔜 ⚠ 🥈 👶). + +👉 💪 📶 ⚠ ⚒ 🆙 **ℹ** 👈 👆 💪 ⚙️ 🎂 📱, & 👈 **💰** 👪 📨, &/⚖️ 👈 👆 💪 **🧹 🆙** ⏮️. 🖼, 💽 🔗 🎱, ⚖️ 🚚 🔗 🎰 🏫 🏷. + +## ⚙️ 💼 + +➡️ ▶️ ⏮️ 🖼 **⚙️ 💼** & ⤴️ 👀 ❔ ❎ ⚫️ ⏮️ 👉. + +➡️ 🌈 👈 👆 ✔️ **🎰 🏫 🏷** 👈 👆 💚 ⚙️ 🍵 📨. 👶 + +🎏 🏷 🔗 👪 📨,, ⚫️ 🚫 1️⃣ 🏷 📍 📨, ⚖️ 1️⃣ 📍 👩‍💻 ⚖️ 🕳 🎏. + +➡️ 🌈 👈 🚚 🏷 💪 **✊ 🕰**, ↩️ ⚫️ ✔️ ✍ 📚 **💽 ⚪️➡️ 💾**. 👆 🚫 💚 ⚫️ 🔠 📨. + +👆 💪 📐 ⚫️ 🔝 🎚 🕹/📁, ✋️ 👈 🔜 ⛓ 👈 ⚫️ 🔜 **📐 🏷** 🚥 👆 🏃‍♂ 🙅 🏧 💯, ⤴️ 👈 💯 🔜 **🐌** ↩️ ⚫️ 🔜 ✔️ ⌛ 🏷 📐 ⏭ 💆‍♂ 💪 🏃 🔬 🍕 📟. + +👈 ⚫️❔ 👥 🔜 ❎, ➡️ 📐 🏷 ⏭ 📨 🍵, ✋️ 🕴 ▶️️ ⏭ 🈸 ▶️ 📨 📨, 🚫 ⏪ 📟 ➖ 📐. + +## 🔆 + +👆 💪 🔬 👉 *🕴* & *🤫* ⚛ ⚙️ `lifespan` 🔢 `FastAPI` 📱, & "🔑 👨‍💼" (👤 🔜 🎦 👆 ⚫️❔ 👈 🥈). + +➡️ ▶️ ⏮️ 🖼 & ⤴️ 👀 ⚫️ ℹ. + +👥 ✍ 🔁 🔢 `lifespan()` ⏮️ `yield` 💖 👉: + +```Python hl_lines="16 19" +{!../../../docs_src/events/tutorial003.py!} +``` + +📥 👥 ⚖ 😥 *🕴* 🛠️ 🚚 🏷 🚮 (❌) 🏷 🔢 📖 ⏮️ 🎰 🏫 🏷 ⏭ `yield`. 👉 📟 🔜 🛠️ **⏭** 🈸 **▶️ ✊ 📨**, ⏮️ *🕴*. + +& ⤴️, ▶️️ ⏮️ `yield`, 👥 🚚 🏷. 👉 📟 🔜 🛠️ **⏮️** 🈸 **🏁 🚚 📨**, ▶️️ ⏭ *🤫*. 👉 💪, 🖼, 🚀 ℹ 💖 💾 ⚖️ 💻. + +!!! tip + `shutdown` 🔜 🔨 🕐❔ 👆 **⛔️** 🈸. + + 🎲 👆 💪 ▶️ 🆕 ⏬, ⚖️ 👆 🤚 🎡 🏃 ⚫️. 🤷 + +### 🔆 🔢 + +🥇 👜 👀, 👈 👥 ⚖ 🔁 🔢 ⏮️ `yield`. 👉 📶 🎏 🔗 ⏮️ `yield`. + +```Python hl_lines="14-19" +{!../../../docs_src/events/tutorial003.py!} +``` + +🥇 🍕 🔢, ⏭ `yield`, 🔜 🛠️ **⏭** 🈸 ▶️. + +& 🍕 ⏮️ `yield` 🔜 🛠️ **⏮️** 🈸 ✔️ 🏁. + +### 🔁 🔑 👨‍💼 + +🚥 👆 ✅, 🔢 🎀 ⏮️ `@asynccontextmanager`. + +👈 🗜 🔢 🔘 🕳 🤙 "**🔁 🔑 👨‍💼**". + +```Python hl_lines="1 13" +{!../../../docs_src/events/tutorial003.py!} +``` + +**🔑 👨‍💼** 🐍 🕳 👈 👆 💪 ⚙️ `with` 📄, 🖼, `open()` 💪 ⚙️ 🔑 👨‍💼: + +```Python +with open("file.txt") as file: + file.read() +``` + +⏮️ ⏬ 🐍, 📤 **🔁 🔑 👨‍💼**. 👆 🔜 ⚙️ ⚫️ ⏮️ `async with`: + +```Python +async with lifespan(app): + await do_stuff() +``` + +🕐❔ 👆 ✍ 🔑 👨‍💼 ⚖️ 🔁 🔑 👨‍💼 💖 🔛, ⚫️❔ ⚫️ 🔨 👈, ⏭ 🛬 `with` 🍫, ⚫️ 🔜 🛠️ 📟 ⏭ `yield`, & ⏮️ ❎ `with` 🍫, ⚫️ 🔜 🛠️ 📟 ⏮️ `yield`. + +👆 📟 🖼 🔛, 👥 🚫 ⚙️ ⚫️ 🔗, ✋️ 👥 🚶‍♀️ ⚫️ FastAPI ⚫️ ⚙️ ⚫️. + +`lifespan` 🔢 `FastAPI` 📱 ✊ **🔁 🔑 👨‍💼**, 👥 💪 🚶‍♀️ 👆 🆕 `lifespan` 🔁 🔑 👨‍💼 ⚫️. + +```Python hl_lines="22" +{!../../../docs_src/events/tutorial003.py!} +``` + +## 🎛 🎉 (😢) + +!!! warning + 👍 🌌 🍵 *🕴* & *🤫* ⚙️ `lifespan` 🔢 `FastAPI` 📱 🔬 🔛. + + 👆 💪 🎲 🚶 👉 🍕. + +📤 🎛 🌌 🔬 👉 ⚛ 🛠️ ⏮️ *🕴* & ⏮️ *🤫*. + +👆 💪 🔬 🎉 🐕‍🦺 (🔢) 👈 💪 🛠️ ⏭ 🈸 ▶️ 🆙, ⚖️ 🕐❔ 🈸 🤫 🔽. + +👫 🔢 💪 📣 ⏮️ `async def` ⚖️ 😐 `def`. + +### `startup` 🎉 + +🚮 🔢 👈 🔜 🏃 ⏭ 🈸 ▶️, 📣 ⚫️ ⏮️ 🎉 `"startup"`: + +```Python hl_lines="8" +{!../../../docs_src/events/tutorial001.py!} +``` + +👉 💼, `startup` 🎉 🐕‍🦺 🔢 🔜 🔢 🏬 "💽" ( `dict`) ⏮️ 💲. + +👆 💪 🚮 🌅 🌘 1️⃣ 🎉 🐕‍🦺 🔢. + +& 👆 🈸 🏆 🚫 ▶️ 📨 📨 ⏭ 🌐 `startup` 🎉 🐕‍🦺 ✔️ 🏁. + +### `shutdown` 🎉 + +🚮 🔢 👈 🔜 🏃 🕐❔ 🈸 🤫 🔽, 📣 ⚫️ ⏮️ 🎉 `"shutdown"`: + +```Python hl_lines="6" +{!../../../docs_src/events/tutorial002.py!} +``` + +📥, `shutdown` 🎉 🐕‍🦺 🔢 🔜 ✍ ✍ ⏸ `"Application shutdown"` 📁 `log.txt`. + +!!! info + `open()` 🔢, `mode="a"` ⛓ "🎻",, ⏸ 🔜 🚮 ⏮️ ⚫️❔ 🔛 👈 📁, 🍵 📁 ⏮️ 🎚. + +!!! tip + 👀 👈 👉 💼 👥 ⚙️ 🐩 🐍 `open()` 🔢 👈 🔗 ⏮️ 📁. + + , ⚫️ 🔌 👤/🅾 (🔢/🔢), 👈 🚚 "⌛" 👜 ✍ 💾. + + ✋️ `open()` 🚫 ⚙️ `async` & `await`. + + , 👥 📣 🎉 🐕‍🦺 🔢 ⏮️ 🐩 `def` ↩️ `async def`. + +!!! info + 👆 💪 ✍ 🌅 🔃 👫 🎉 🐕‍🦺 💃 🎉' 🩺. + +### `startup` & `shutdown` 👯‍♂️ + +📤 ↕ 🤞 👈 ⚛ 👆 *🕴* & *🤫* 🔗, 👆 💪 💚 ▶️ 🕳 & ⤴️ 🏁 ⚫️, 📎 ℹ & ⤴️ 🚀 ⚫️, ♒️. + +🔨 👈 👽 🔢 👈 🚫 💰 ⚛ ⚖️ 🔢 👯‍♂️ 🌅 ⚠ 👆 🔜 💪 🏪 💲 🌐 🔢 ⚖️ 🎏 🎱. + +↩️ 👈, ⚫️ 🔜 👍 ↩️ ⚙️ `lifespan` 🔬 🔛. + +## 📡 ℹ + +📡 ℹ 😟 🤓. 👶 + +🔘, 🔫 📡 🔧, 👉 🍕 🔆 🛠️, & ⚫️ 🔬 🎉 🤙 `startup` & `shutdown`. + +## 🎧 🈸 + +👶 ✔️ 🤯 👈 👫 🔆 🎉 (🕴 & 🤫) 🔜 🕴 🛠️ 👑 🈸, 🚫 [🎧 🈸 - 🗻](./sub-applications.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/extending-openapi.md b/docs/em/docs/advanced/extending-openapi.md new file mode 100644 index 0000000000..496a8d9de3 --- /dev/null +++ b/docs/em/docs/advanced/extending-openapi.md @@ -0,0 +1,314 @@ +# ↔ 🗄 + +!!! warning + 👉 👍 🏧 ⚒. 👆 🎲 💪 🚶 ⚫️. + + 🚥 👆 📄 🔰 - 👩‍💻 🦮, 👆 💪 🎲 🚶 👉 📄. + + 🚥 👆 ⏪ 💭 👈 👆 💪 🔀 🏗 🗄 🔗, 😣 👂. + +📤 💼 🌐❔ 👆 💪 💪 🔀 🏗 🗄 🔗. + +👉 📄 👆 🔜 👀 ❔. + +## 😐 🛠️ + +😐 (🔢) 🛠️, ⏩. + +`FastAPI` 🈸 (👐) ✔️ `.openapi()` 👩‍🔬 👈 📈 📨 🗄 🔗. + +🍕 🈸 🎚 🏗, *➡ 🛠️* `/openapi.json` (⚖️ ⚫️❔ 👆 ⚒ 👆 `openapi_url`) ®. + +⚫️ 📨 🎻 📨 ⏮️ 🏁 🈸 `.openapi()` 👩‍🔬. + +🔢, ⚫️❔ 👩‍🔬 `.openapi()` 🔨 ✅ 🏠 `.openapi_schema` 👀 🚥 ⚫️ ✔️ 🎚 & 📨 👫. + +🚥 ⚫️ 🚫, ⚫️ 🏗 👫 ⚙️ 🚙 🔢 `fastapi.openapi.utils.get_openapi`. + +& 👈 🔢 `get_openapi()` 📨 🔢: + +* `title`: 🗄 📛, 🎦 🩺. +* `version`: ⏬ 👆 🛠️, ✅ `2.5.0`. +* `openapi_version`: ⏬ 🗄 🔧 ⚙️. 🔢, ⏪: `3.0.2`. +* `description`: 📛 👆 🛠️. +* `routes`: 📇 🛣, 👫 🔠 ® *➡ 🛠️*. 👫 ✊ ⚪️➡️ `app.routes`. + +## 🔑 🔢 + +⚙️ ℹ 🔛, 👆 💪 ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗 & 🔐 🔠 🍕 👈 👆 💪. + +🖼, ➡️ 🚮 📄 🗄 ↔ 🔌 🛃 🔱. + +### 😐 **FastAPI** + +🥇, ✍ 🌐 👆 **FastAPI** 🈸 🛎: + +```Python hl_lines="1 4 7-9" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 🏗 🗄 🔗 + +⤴️, ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗, 🔘 `custom_openapi()` 🔢: + +```Python hl_lines="2 15-20" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 🔀 🗄 🔗 + +🔜 👆 💪 🚮 📄 ↔, ❎ 🛃 `x-logo` `info` "🎚" 🗄 🔗: + +```Python hl_lines="21-23" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 💾 🗄 🔗 + +👆 💪 ⚙️ 🏠 `.openapi_schema` "💾", 🏪 👆 🏗 🔗. + +👈 🌌, 👆 🈸 🏆 🚫 ✔️ 🏗 🔗 🔠 🕰 👩‍💻 📂 👆 🛠️ 🩺. + +⚫️ 🔜 🏗 🕴 🕐, & ⤴️ 🎏 💾 🔗 🔜 ⚙️ ⏭ 📨. + +```Python hl_lines="13-14 24-25" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### 🔐 👩‍🔬 + +🔜 👆 💪 ❎ `.openapi()` 👩‍🔬 ⏮️ 👆 🆕 🔢. + +```Python hl_lines="28" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### ✅ ⚫️ + +🕐 👆 🚶 http://127.0.0.1:8000/redoc 👆 🔜 👀 👈 👆 ⚙️ 👆 🛃 🔱 (👉 🖼, **FastAPI**'Ⓜ 🔱): + + + +## 👤-🕸 🕸 & 🎚 🩺 + +🛠️ 🩺 ⚙️ **🦁 🎚** & **📄**, & 🔠 👈 💪 🕸 & 🎚 📁. + +🔢, 👈 📁 🍦 ⚪️➡️ 💲. + +✋️ ⚫️ 💪 🛃 ⚫️, 👆 💪 ⚒ 🎯 💲, ⚖️ 🍦 📁 👆. + +👈 ⚠, 🖼, 🚥 👆 💪 👆 📱 🚧 👷 ⏪ 📱, 🍵 📂 🕸 🔐, ⚖️ 🇧🇿 🕸. + +📥 👆 🔜 👀 ❔ 🍦 👈 📁 👆, 🎏 FastAPI 📱, & 🔗 🩺 ⚙️ 👫. + +### 🏗 📁 📊 + +➡️ 💬 👆 🏗 📁 📊 👀 💖 👉: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +🔜 ✍ 📁 🏪 📚 🎻 📁. + +👆 🆕 📁 📊 💪 👀 💖 👉: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### ⏬ 📁 + +⏬ 🎻 📁 💪 🩺 & 🚮 👫 🔛 👈 `static/` 📁. + +👆 💪 🎲 ▶️️-🖊 🔠 🔗 & 🖊 🎛 🎏 `Save link as...`. + +**🦁 🎚** ⚙️ 📁: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +& **📄** ⚙️ 📁: + +* `redoc.standalone.js` + +⏮️ 👈, 👆 📁 📊 💪 👀 💖: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### 🍦 🎻 📁 + +* 🗄 `StaticFiles`. +* "🗻" `StaticFiles()` 👐 🎯 ➡. + +```Python hl_lines="7 11" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +### 💯 🎻 📁 + +▶️ 👆 🈸 & 🚶 http://127.0.0.1:8000/static/redoc.standalone.js. + +👆 🔜 👀 📶 📏 🕸 📁 **📄**. + +⚫️ 💪 ▶️ ⏮️ 🕳 💖: + +```JavaScript +/*! + * ReDoc - OpenAPI/Swagger-generated API Reference Documentation + * ------------------------------------------------------------- + * Version: "2.0.0-rc.18" + * Repo: https://github.com/Redocly/redoc + */ +!function(e,t){"object"==typeof exports&&"object"==typeof m + +... +``` + +👈 ✔ 👈 👆 💆‍♂ 💪 🍦 🎻 📁 ⚪️➡️ 👆 📱, & 👈 👆 🥉 🎻 📁 🩺 ☑ 🥉. + +🔜 👥 💪 🔗 📱 ⚙️ 📚 🎻 📁 🩺. + +### ❎ 🏧 🩺 + +🥇 🔁 ❎ 🏧 🩺, 📚 ⚙️ 💲 🔢. + +❎ 👫, ⚒ 👫 📛 `None` 🕐❔ 🏗 👆 `FastAPI` 📱: + +```Python hl_lines="9" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +### 🔌 🛃 🩺 + +🔜 👆 💪 ✍ *➡ 🛠️* 🛃 🩺. + +👆 💪 🏤-⚙️ FastAPI 🔗 🔢 ✍ 🕸 📃 🩺, & 🚶‍♀️ 👫 💪 ❌: + +* `openapi_url`: 📛 🌐❔ 🕸 📃 🩺 💪 🤚 🗄 🔗 👆 🛠️. 👆 💪 ⚙️ 📥 🔢 `app.openapi_url`. +* `title`: 📛 👆 🛠️. +* `oauth2_redirect_url`: 👆 💪 ⚙️ `app.swagger_ui_oauth2_redirect_url` 📥 ⚙️ 🔢. +* `swagger_js_url`: 📛 🌐❔ 🕸 👆 🦁 🎚 🩺 💪 🤚 **🕸** 📁. 👉 1️⃣ 👈 👆 👍 📱 🔜 🍦. +* `swagger_css_url`: 📛 🌐❔ 🕸 👆 🦁 🎚 🩺 💪 🤚 **🎚** 📁. 👉 1️⃣ 👈 👆 👍 📱 🔜 🍦. + +& ➡ 📄... + +```Python hl_lines="2-6 14-22 25-27 30-36" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +!!! tip + *➡ 🛠️* `swagger_ui_redirect` 👩‍🎓 🕐❔ 👆 ⚙️ Oauth2️⃣. + + 🚥 👆 🛠️ 👆 🛠️ ⏮️ Oauth2️⃣ 🐕‍🦺, 👆 🔜 💪 🔓 & 👟 🔙 🛠️ 🩺 ⏮️ 📎 🎓. & 🔗 ⏮️ ⚫️ ⚙️ 🎰 Oauth2️⃣ 🤝. + + 🦁 🎚 🔜 🍵 ⚫️ ⛅ 🎑 👆, ✋️ ⚫️ 💪 👉 "❎" 👩‍🎓. + +### ✍ *➡ 🛠️* 💯 ⚫️ + +🔜, 💪 💯 👈 🌐 👷, ✍ *➡ 🛠️*: + +```Python hl_lines="39-41" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +### 💯 ⚫️ + +🔜, 👆 🔜 💪 🔌 👆 📻, 🚶 👆 🩺 http://127.0.0.1:8000/docs, & 🔃 📃. + +& 🍵 🕸, 👆 🔜 💪 👀 🩺 👆 🛠️ & 🔗 ⏮️ ⚫️. + +## 🛠️ 🦁 🎚 + +👆 💪 🔗 ➕ 🦁 🎚 🔢. + +🔗 👫, 🚶‍♀️ `swagger_ui_parameters` ❌ 🕐❔ 🏗 `FastAPI()` 📱 🎚 ⚖️ `get_swagger_ui_html()` 🔢. + +`swagger_ui_parameters` 📨 📖 ⏮️ 📳 🚶‍♀️ 🦁 🎚 🔗. + +FastAPI 🗜 📳 **🎻** ⚒ 👫 🔗 ⏮️ 🕸, 👈 ⚫️❔ 🦁 🎚 💪. + +### ❎ ❕ 🎦 + +🖼, 👆 💪 ❎ ❕ 🎦 🦁 🎚. + +🍵 🔀 ⚒, ❕ 🎦 🛠️ 🔢: + + + +✋️ 👆 💪 ❎ ⚫️ ⚒ `syntaxHighlight` `False`: + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial003.py!} +``` + +...& ⤴️ 🦁 🎚 🏆 🚫 🎦 ❕ 🎦 🚫🔜: + + + +### 🔀 🎢 + +🎏 🌌 👆 💪 ⚒ ❕ 🎦 🎢 ⏮️ 🔑 `"syntaxHighlight.theme"` (👀 👈 ⚫️ ✔️ ❣ 🖕): + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial004.py!} +``` + +👈 📳 🔜 🔀 ❕ 🎦 🎨 🎢: + + + +### 🔀 🔢 🦁 🎚 🔢 + +FastAPI 🔌 🔢 📳 🔢 ☑ 🌅 ⚙️ 💼. + +⚫️ 🔌 👫 🔢 📳: + +```Python +{!../../../fastapi/openapi/docs.py[ln:7-13]!} +``` + +👆 💪 🔐 🙆 👫 ⚒ 🎏 💲 ❌ `swagger_ui_parameters`. + +🖼, ❎ `deepLinking` 👆 💪 🚶‍♀️ 👉 ⚒ `swagger_ui_parameters`: + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial005.py!} +``` + +### 🎏 🦁 🎚 🔢 + +👀 🌐 🎏 💪 📳 👆 💪 ⚙️, ✍ 🛂 🩺 🦁 🎚 🔢. + +### 🕸-🕴 ⚒ + +🦁 🎚 ✔ 🎏 📳 **🕸-🕴** 🎚 (🖼, 🕸 🔢). + +FastAPI 🔌 👫 🕸-🕴 `presets` ⚒: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +👫 **🕸** 🎚, 🚫 🎻, 👆 💪 🚫 🚶‍♀️ 👫 ⚪️➡️ 🐍 📟 🔗. + +🚥 👆 💪 ⚙️ 🕸-🕴 📳 💖 📚, 👆 💪 ⚙️ 1️⃣ 👩‍🔬 🔛. 🔐 🌐 🦁 🎚 *➡ 🛠️* & ❎ ✍ 🙆 🕸 👆 💪. diff --git a/docs/em/docs/advanced/generate-clients.md b/docs/em/docs/advanced/generate-clients.md new file mode 100644 index 0000000000..30560c8c65 --- /dev/null +++ b/docs/em/docs/advanced/generate-clients.md @@ -0,0 +1,267 @@ +# 🏗 👩‍💻 + +**FastAPI** ⚓️ 🔛 🗄 🔧, 👆 🤚 🏧 🔗 ⏮️ 📚 🧰, 🔌 🏧 🛠️ 🩺 (🚚 🦁 🎚). + +1️⃣ 🎯 📈 👈 🚫 🎯 ⭐ 👈 👆 💪 **🏗 👩‍💻** (🕣 🤙 **📱** ) 👆 🛠️, 📚 🎏 **🛠️ 🇪🇸**. + +## 🗄 👩‍💻 🚂 + +📤 📚 🧰 🏗 👩‍💻 ⚪️➡️ **🗄**. + +⚠ 🧰 🗄 🚂. + +🚥 👆 🏗 **🕸**, 📶 😌 🎛 🗄-📕-🇦🇪. + +## 🏗 📕 🕸 👩‍💻 + +➡️ ▶️ ⏮️ 🙅 FastAPI 🈸: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9-11 14-15 18 19 23" + {!> ../../../docs_src/generate_clients/tutorial001.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="7-9 12-13 16-17 21" + {!> ../../../docs_src/generate_clients/tutorial001_py39.py!} + ``` + +👀 👈 *➡ 🛠️* 🔬 🏷 👫 ⚙️ 📨 🚀 & 📨 🚀, ⚙️ 🏷 `Item` & `ResponseMessage`. + +### 🛠️ 🩺 + +🚥 👆 🚶 🛠️ 🩺, 👆 🔜 👀 👈 ⚫️ ✔️ **🔗** 📊 📨 📨 & 📨 📨: + + + +👆 💪 👀 👈 🔗 ↩️ 👫 📣 ⏮️ 🏷 📱. + +👈 ℹ 💪 📱 **🗄 🔗**, & ⤴️ 🎦 🛠️ 🩺 (🦁 🎚). + +& 👈 🎏 ℹ ⚪️➡️ 🏷 👈 🔌 🗄 ⚫️❔ 💪 ⚙️ **🏗 👩‍💻 📟**. + +### 🏗 📕 👩‍💻 + +🔜 👈 👥 ✔️ 📱 ⏮️ 🏷, 👥 💪 🏗 👩‍💻 📟 🕸. + +#### ❎ `openapi-typescript-codegen` + +👆 💪 ❎ `openapi-typescript-codegen` 👆 🕸 📟 ⏮️: + +
+ +```console +$ npm install openapi-typescript-codegen --save-dev + +---> 100% +``` + +
+ +#### 🏗 👩‍💻 📟 + +🏗 👩‍💻 📟 👆 💪 ⚙️ 📋 ⏸ 🈸 `openapi` 👈 🔜 🔜 ❎. + +↩️ ⚫️ ❎ 🇧🇿 🏗, 👆 🎲 🚫🔜 💪 🤙 👈 📋 🔗, ✋️ 👆 🔜 🚮 ⚫️ 🔛 👆 `package.json` 📁. + +⚫️ 💪 👀 💖 👉: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi --input http://localhost:8000/openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "openapi-typescript-codegen": "^0.20.1", + "typescript": "^4.6.2" + } +} +``` + +⏮️ ✔️ 👈 ☕ `generate-client` ✍ 📤, 👆 💪 🏃 ⚫️ ⏮️: + +
+ +```console +$ npm run generate-client + +frontend-app@1.0.0 generate-client /home/user/code/frontend-app +> openapi --input http://localhost:8000/openapi.json --output ./src/client --client axios +``` + +
+ +👈 📋 🔜 🏗 📟 `./src/client` & 🔜 ⚙️ `axios` (🕸 🇺🇸🔍 🗃) 🔘. + +### 🔄 👅 👩‍💻 📟 + +🔜 👆 💪 🗄 & ⚙️ 👩‍💻 📟, ⚫️ 💪 👀 💖 👉, 👀 👈 👆 🤚 ✍ 👩‍🔬: + + + +👆 🔜 🤚 ✍ 🚀 📨: + + + +!!! tip + 👀 ✍ `name` & `price`, 👈 🔬 FastAPI 🈸, `Item` 🏷. + +👆 🔜 ✔️ ⏸ ❌ 📊 👈 👆 📨: + + + +📨 🎚 🔜 ✔️ ✍: + + + +## FastAPI 📱 ⏮️ 🔖 + +📚 💼 👆 FastAPI 📱 🔜 🦏, & 👆 🔜 🎲 ⚙️ 🔖 🎏 🎏 👪 *➡ 🛠️*. + +🖼, 👆 💪 ✔️ 📄 **🏬** & ➕1️⃣ 📄 **👩‍💻**, & 👫 💪 👽 🔖: + + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="23 28 36" + {!> ../../../docs_src/generate_clients/tutorial002.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="21 26 34" + {!> ../../../docs_src/generate_clients/tutorial002_py39.py!} + ``` + +### 🏗 📕 👩‍💻 ⏮️ 🔖 + +🚥 👆 🏗 👩‍💻 FastAPI 📱 ⚙️ 🔖, ⚫️ 🔜 🛎 🎏 👩‍💻 📟 ⚓️ 🔛 🔖. + +👉 🌌 👆 🔜 💪 ✔️ 👜 ✔ & 👪 ☑ 👩‍💻 📟: + + + +👉 💼 👆 ✔️: + +* `ItemsService` +* `UsersService` + +### 👩‍💻 👩‍🔬 📛 + +▶️️ 🔜 🏗 👩‍🔬 📛 💖 `createItemItemsPost` 🚫 👀 📶 🧹: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...👈 ↩️ 👩‍💻 🚂 ⚙️ 🗄 🔗 **🛠️ 🆔** 🔠 *➡ 🛠️*. + +🗄 🚚 👈 🔠 🛠️ 🆔 😍 🤭 🌐 *➡ 🛠️*, FastAPI ⚙️ **🔢 📛**, **➡**, & **🇺🇸🔍 👩‍🔬/🛠️** 🏗 👈 🛠️ 🆔, ↩️ 👈 🌌 ⚫️ 💪 ⚒ 💭 👈 🛠️ 🆔 😍. + +✋️ 👤 🔜 🎦 👆 ❔ 📉 👈 ⏭. 👶 + +## 🛃 🛠️ 🆔 & 👍 👩‍🔬 📛 + +👆 💪 **🔀** 🌌 👫 🛠️ 🆔 **🏗** ⚒ 👫 🙅 & ✔️ **🙅 👩‍🔬 📛** 👩‍💻. + +👉 💼 👆 🔜 ✔️ 🚚 👈 🔠 🛠️ 🆔 **😍** 🎏 🌌. + +🖼, 👆 💪 ⚒ 💭 👈 🔠 *➡ 🛠️* ✔️ 🔖, & ⤴️ 🏗 🛠️ 🆔 ⚓️ 🔛 **🔖** & *➡ 🛠️* **📛** (🔢 📛). + +### 🛃 🏗 😍 🆔 🔢 + +FastAPI ⚙️ **😍 🆔** 🔠 *➡ 🛠️*, ⚫️ ⚙️ **🛠️ 🆔** & 📛 🙆 💪 🛃 🏷, 📨 ⚖️ 📨. + +👆 💪 🛃 👈 🔢. ⚫️ ✊ `APIRoute` & 🔢 🎻. + +🖼, 📥 ⚫️ ⚙️ 🥇 🔖 (👆 🔜 🎲 ✔️ 🕴 1️⃣ 🔖) & *➡ 🛠️* 📛 (🔢 📛). + +👆 💪 ⤴️ 🚶‍♀️ 👈 🛃 🔢 **FastAPI** `generate_unique_id_function` 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="8-9 12" + {!> ../../../docs_src/generate_clients/tutorial003.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="6-7 10" + {!> ../../../docs_src/generate_clients/tutorial003_py39.py!} + ``` + +### 🏗 📕 👩‍💻 ⏮️ 🛃 🛠️ 🆔 + +🔜 🚥 👆 🏗 👩‍💻 🔄, 👆 🔜 👀 👈 ⚫️ ✔️ 📉 👩‍🔬 📛: + + + +👆 👀, 👩‍🔬 📛 🔜 ✔️ 🔖 & ⤴️ 🔢 📛, 🔜 👫 🚫 🔌 ℹ ⚪️➡️ 📛 ➡ & 🇺🇸🔍 🛠️. + +### 🗜 🗄 🔧 👩‍💻 🚂 + +🏗 📟 ✔️ **❎ ℹ**. + +👥 ⏪ 💭 👈 👉 👩‍🔬 🔗 **🏬** ↩️ 👈 🔤 `ItemsService` (✊ ⚪️➡️ 🔖), ✋️ 👥 ✔️ 📛 🔡 👩‍🔬 📛 💁‍♂️. 👶 + +👥 🔜 🎲 💚 🚧 ⚫️ 🗄 🏢, 👈 🔜 🚚 👈 🛠️ 🆔 **😍**. + +✋️ 🏗 👩‍💻 👥 💪 **🔀** 🗄 🛠️ 🆔 ▶️️ ⏭ 🏭 👩‍💻, ⚒ 👈 👩‍🔬 📛 👌 & **🧹**. + +👥 💪 ⏬ 🗄 🎻 📁 `openapi.json` & ⤴️ 👥 💪 **❎ 👈 🔡 🔖** ⏮️ ✍ 💖 👉: + +```Python +{!../../../docs_src/generate_clients/tutorial004.py!} +``` + +⏮️ 👈, 🛠️ 🆔 🔜 📁 ⚪️➡️ 👜 💖 `items-get_items` `get_items`, 👈 🌌 👩‍💻 🚂 💪 🏗 🙅 👩‍🔬 📛. + +### 🏗 📕 👩‍💻 ⏮️ 🗜 🗄 + +🔜 🔚 🏁 📁 `openapi.json`, 👆 🔜 🔀 `package.json` ⚙️ 👈 🇧🇿 📁, 🖼: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi --input ./openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "openapi-typescript-codegen": "^0.20.1", + "typescript": "^4.6.2" + } +} +``` + +⏮️ 🏭 🆕 👩‍💻, 👆 🔜 🔜 ✔️ **🧹 👩‍🔬 📛**, ⏮️ 🌐 **✍**, **⏸ ❌**, ♒️: + + + +## 💰 + +🕐❔ ⚙️ 🔁 🏗 👩‍💻 👆 🔜 **✍** : + +* 👩‍🔬. +* 📨 🚀 💪, 🔢 🔢, ♒️. +* 📨 🚀. + +👆 🔜 ✔️ **⏸ ❌** 🌐. + +& 🕐❔ 👆 ℹ 👩‍💻 📟, & **♻** 🕸, ⚫️ 🔜 ✔️ 🙆 🆕 *➡ 🛠️* 💪 👩‍🔬, 🗝 🕐 ❎, & 🙆 🎏 🔀 🔜 🎨 🔛 🏗 📟. 👶 + +👉 ⛓ 👈 🚥 🕳 🔀 ⚫️ 🔜 **🎨** 🔛 👩‍💻 📟 🔁. & 🚥 👆 **🏗** 👩‍💻 ⚫️ 🔜 ❌ 👅 🚥 👆 ✔️ 🙆 **🔖** 📊 ⚙️. + +, 👆 🔜 **🔍 📚 ❌** 📶 ⏪ 🛠️ 🛵 ↩️ ✔️ ⌛ ❌ 🎦 🆙 👆 🏁 👩‍💻 🏭 & ⤴️ 🔄 ℹ 🌐❔ ⚠. 👶 diff --git a/docs/em/docs/advanced/graphql.md b/docs/em/docs/advanced/graphql.md new file mode 100644 index 0000000000..8509643ce8 --- /dev/null +++ b/docs/em/docs/advanced/graphql.md @@ -0,0 +1,56 @@ +# 🕹 + +**FastAPI** ⚓️ 🔛 **🔫** 🐩, ⚫️ 📶 ⏩ 🛠️ 🙆 **🕹** 🗃 🔗 ⏮️ 🔫. + +👆 💪 🌀 😐 FastAPI *➡ 🛠️* ⏮️ 🕹 🔛 🎏 🈸. + +!!! tip + **🕹** ❎ 📶 🎯 ⚙️ 💼. + + ⚫️ ✔️ **📈** & **⚠** 🕐❔ 🔬 ⚠ **🕸 🔗**. + + ⚒ 💭 👆 🔬 🚥 **💰** 👆 ⚙️ 💼 ⚖ **👐**. 👶 + +## 🕹 🗃 + +📥 **🕹** 🗃 👈 ✔️ **🔫** 🐕‍🦺. 👆 💪 ⚙️ 👫 ⏮️ **FastAPI**: + +* 🍓 👶 + * ⏮️ 🩺 FastAPI +* 👸 + * ⏮️ 🩺 💃 (👈 ✔ FastAPI) +* 🍟 + * ⏮️ 🍟 🔫 🚚 🔫 🛠️ +* + * ⏮️ 💃-Graphene3️⃣ + +## 🕹 ⏮️ 🍓 + +🚥 👆 💪 ⚖️ 💚 👷 ⏮️ **🕹**, **🍓** **👍** 🗃 ⚫️ ✔️ 🔧 🔐 **FastAPI** 🔧, ⚫️ 🌐 ⚓️ 🔛 **🆎 ✍**. + +⚓️ 🔛 👆 ⚙️ 💼, 👆 5️⃣📆 💖 ⚙️ 🎏 🗃, ✋️ 🚥 👆 💭 👤, 👤 🔜 🎲 🤔 👆 🔄 **🍓**. + +📥 🤪 🎮 ❔ 👆 💪 🛠️ 🍓 ⏮️ FastAPI: + +```Python hl_lines="3 22 25-26" +{!../../../docs_src/graphql/tutorial001.py!} +``` + +👆 💪 💡 🌅 🔃 🍓 🍓 🧾. + +& 🩺 🔃 🍓 ⏮️ FastAPI. + +## 🗝 `GraphQLApp` ⚪️➡️ 💃 + +⏮️ ⏬ 💃 🔌 `GraphQLApp` 🎓 🛠️ ⏮️ . + +⚫️ 😢 ⚪️➡️ 💃, ✋️ 🚥 👆 ✔️ 📟 👈 ⚙️ ⚫️, 👆 💪 💪 **↔** 💃-Graphene3️⃣, 👈 📔 🎏 ⚙️ 💼 & ✔️ **🌖 🌓 🔢**. + +!!! tip + 🚥 👆 💪 🕹, 👤 🔜 👍 👆 ✅ 👅 🍓, ⚫️ ⚓️ 🔛 🆎 ✍ ↩️ 🛃 🎓 & 🆎. + +## 💡 🌅 + +👆 💪 💡 🌅 🔃 **🕹** 🛂 🕹 🧾. + +👆 💪 ✍ 🌅 🔃 🔠 👈 🗃 🔬 🔛 👫 🔗. diff --git a/docs/em/docs/advanced/index.md b/docs/em/docs/advanced/index.md new file mode 100644 index 0000000000..abe8d357c9 --- /dev/null +++ b/docs/em/docs/advanced/index.md @@ -0,0 +1,24 @@ +# 🏧 👩‍💻 🦮 + +## 🌖 ⚒ + +👑 [🔰 - 👩‍💻 🦮](../tutorial/){.internal-link target=_blank} 🔜 🥃 🤝 👆 🎫 🔘 🌐 👑 ⚒ **FastAPI**. + +⏭ 📄 👆 🔜 👀 🎏 🎛, 📳, & 🌖 ⚒. + +!!! tip + ⏭ 📄 **🚫 🎯 "🏧"**. + + & ⚫️ 💪 👈 👆 ⚙️ 💼, ⚗ 1️⃣ 👫. + +## ✍ 🔰 🥇 + +👆 💪 ⚙️ 🏆 ⚒ **FastAPI** ⏮️ 💡 ⚪️➡️ 👑 [🔰 - 👩‍💻 🦮](../tutorial/){.internal-link target=_blank}. + +& ⏭ 📄 🤔 👆 ⏪ ✍ ⚫️, & 🤔 👈 👆 💭 👈 👑 💭. + +## 🏎.🅾 ↗️ + +🚥 👆 🔜 💖 ✊ 🏧-🔰 ↗️ 🔗 👉 📄 🩺, 👆 💪 💚 ✅: 💯-💾 🛠️ ⏮️ FastAPI & ☁ **🏎.🅾**. + +👫 ⏳ 🩸 1️⃣0️⃣ 💯 🌐 💰 🛠️ **FastAPI**. 👶 👶 diff --git a/docs/em/docs/advanced/middleware.md b/docs/em/docs/advanced/middleware.md new file mode 100644 index 0000000000..b3e722ed08 --- /dev/null +++ b/docs/em/docs/advanced/middleware.md @@ -0,0 +1,99 @@ +# 🏧 🛠️ + +👑 🔰 👆 ✍ ❔ 🚮 [🛃 🛠️](../tutorial/middleware.md){.internal-link target=_blank} 👆 🈸. + +& ⤴️ 👆 ✍ ❔ 🍵 [⚜ ⏮️ `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. + +👉 📄 👥 🔜 👀 ❔ ⚙️ 🎏 🛠️. + +## ❎ 🔫 🛠️ + +**FastAPI** ⚓️ 🔛 💃 & 🛠️ 🔫 🔧, 👆 💪 ⚙️ 🙆 🔫 🛠️. + +🛠️ 🚫 ✔️ ⚒ FastAPI ⚖️ 💃 👷, 📏 ⚫️ ⏩ 🔫 🔌. + +🏢, 🔫 🛠️ 🎓 👈 ⌛ 📨 🔫 📱 🥇 ❌. + +, 🧾 🥉-🥳 🔫 🛠️ 👫 🔜 🎲 💬 👆 🕳 💖: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +✋️ FastAPI (🤙 💃) 🚚 🙅 🌌 ⚫️ 👈 ⚒ 💭 👈 🔗 🛠️ 🍵 💽 ❌ & 🛃 ⚠ 🐕‍🦺 👷 ☑. + +👈, 👆 ⚙️ `app.add_middleware()` (🖼 ⚜). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` 📨 🛠️ 🎓 🥇 ❌ & 🙆 🌖 ❌ 🚶‍♀️ 🛠️. + +## 🛠️ 🛠️ + +**FastAPI** 🔌 📚 🛠️ ⚠ ⚙️ 💼, 👥 🔜 👀 ⏭ ❔ ⚙️ 👫. + +!!! note "📡 ℹ" + ⏭ 🖼, 👆 💪 ⚙️ `from starlette.middleware.something import SomethingMiddleware`. + + **FastAPI** 🚚 📚 🛠️ `fastapi.middleware` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 🛠️ 👟 🔗 ⚪️➡️ 💃. + +## `HTTPSRedirectMiddleware` + +🛠️ 👈 🌐 📨 📨 🔜 👯‍♂️ `https` ⚖️ `wss`. + +🙆 📨 📨 `http` ⚖️ `ws` 🔜 ❎ 🔐 ⚖ ↩️. + +```Python hl_lines="2 6" +{!../../../docs_src/advanced_middleware/tutorial001.py!} +``` + +## `TrustedHostMiddleware` + +🛠️ 👈 🌐 📨 📨 ✔️ ☑ ⚒ `Host` 🎚, ✔ 💂‍♂ 🛡 🇺🇸🔍 🦠 🎚 👊. + +```Python hl_lines="2 6-8" +{!../../../docs_src/advanced_middleware/tutorial002.py!} +``` + +📄 ❌ 🐕‍🦺: + +* `allowed_hosts` - 📇 🆔 📛 👈 🔜 ✔ 📛. 🃏 🆔 ✅ `*.example.com` 🐕‍🦺 🎀 📁. ✔ 🙆 📛 👯‍♂️ ⚙️ `allowed_hosts=["*"]` ⚖️ 🚫 🛠️. + +🚥 📨 📨 🔨 🚫 ✔ ☑ ⤴️ `400` 📨 🔜 📨. + +## `GZipMiddleware` + +🍵 🗜 📨 🙆 📨 👈 🔌 `"gzip"` `Accept-Encoding` 🎚. + +🛠️ 🔜 🍵 👯‍♂️ 🐩 & 🎥 📨. + +```Python hl_lines="2 6" +{!../../../docs_src/advanced_middleware/tutorial003.py!} +``` + +📄 ❌ 🐕‍🦺: + +* `minimum_size` - 🚫 🗜 📨 👈 🤪 🌘 👉 💯 📐 🔢. 🔢 `500`. + +## 🎏 🛠️ + +📤 📚 🎏 🔫 🛠️. + +🖼: + +* 🔫 +* Uvicorn `ProxyHeadersMiddleware` +* 🇸🇲 + +👀 🎏 💪 🛠️ ✅ 💃 🛠️ 🩺 & 🔫 👌 📇. diff --git a/docs/em/docs/advanced/nosql-databases.md b/docs/em/docs/advanced/nosql-databases.md new file mode 100644 index 0000000000..9c828a9094 --- /dev/null +++ b/docs/em/docs/advanced/nosql-databases.md @@ -0,0 +1,156 @@ +# ☁ (📎 / 🦏 💽) 💽 + +**FastAPI** 💪 🛠️ ⏮️ 🙆 . + +📥 👥 🔜 👀 🖼 ⚙️ **🗄**, 📄 🧢 ☁ 💽. + +👆 💪 🛠️ ⚫️ 🙆 🎏 ☁ 💽 💖: + +* **✳** +* **👸** +* **✳** +* **🇸🇲** +* **✳**, ♒️. + +!!! tip + 📤 🛂 🏗 🚂 ⏮️ **FastAPI** & **🗄**, 🌐 ⚓️ 🔛 **☁**, 🔌 🕸 & 🌖 🧰: https://github.com/tiangolo/full-stack-fastapi-couchbase + +## 🗄 🗄 🦲 + +🔜, 🚫 💸 🙋 🎂, 🕴 🗄: + +```Python hl_lines="3-5" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## 🔬 📉 ⚙️ "📄 🆎" + +👥 🔜 ⚙️ ⚫️ ⏪ 🔧 🏑 `type` 👆 📄. + +👉 🚫 ✔ 🗄, ✋️ 👍 💡 👈 🔜 ℹ 👆 ⏮️. + +```Python hl_lines="9" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## 🚮 🔢 🤚 `Bucket` + +**🗄**, 🥡 ⚒ 📄, 👈 💪 🎏 🆎. + +👫 🛎 🌐 🔗 🎏 🈸. + +🔑 🔗 💽 🌏 🔜 "💽" (🎯 💽, 🚫 💽 💽). + +🔑 **✳** 🔜 "🗃". + +📟, `Bucket` 🎨 👑 🇨🇻 📻 ⏮️ 💽. + +👉 🚙 🔢 🔜: + +* 🔗 **🗄** 🌑 (👈 💪 👁 🎰). + * ⚒ 🔢 ⏲. +* 🔓 🌑. +* 🤚 `Bucket` 👐. + * ⚒ 🔢 ⏲. +* 📨 ⚫️. + +```Python hl_lines="12-21" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## ✍ Pydantic 🏷 + +**🗄** "📄" 🤙 "🎻 🎚", 👥 💪 🏷 👫 ⏮️ Pydantic. + +### `User` 🏷 + +🥇, ➡️ ✍ `User` 🏷: + +```Python hl_lines="24-28" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +👥 🔜 ⚙️ 👉 🏷 👆 *➡ 🛠️ 🔢*,, 👥 🚫 🔌 ⚫️ `hashed_password`. + +### `UserInDB` 🏷 + +🔜, ➡️ ✍ `UserInDB` 🏷. + +👉 🔜 ✔️ 💽 👈 🤙 🏪 💽. + +👥 🚫 ✍ ⚫️ 🏿 Pydantic `BaseModel` ✋️ 🏿 👆 👍 `User`, ↩️ ⚫️ 🔜 ✔️ 🌐 🔢 `User` ➕ 👩‍❤‍👨 🌅: + +```Python hl_lines="31-33" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +!!! note + 👀 👈 👥 ✔️ `hashed_password` & `type` 🏑 👈 🔜 🏪 💽. + + ✋️ ⚫️ 🚫 🍕 🏢 `User` 🏷 (1️⃣ 👥 🔜 📨 *➡ 🛠️*). + +## 🤚 👩‍💻 + +🔜 ✍ 🔢 👈 🔜: + +* ✊ 🆔. +* 🏗 📄 🆔 ⚪️➡️ ⚫️. +* 🤚 📄 ⏮️ 👈 🆔. +* 🚮 🎚 📄 `UserInDB` 🏷. + +🏗 🔢 👈 🕴 💡 🤚 👆 👩‍💻 ⚪️➡️ `username` (⚖️ 🙆 🎏 🔢) 🔬 👆 *➡ 🛠️ 🔢*, 👆 💪 🌖 💪 🏤-⚙️ ⚫️ 💗 🍕 & 🚮 ⚒ 💯 ⚫️: + +```Python hl_lines="36-42" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +### Ⓜ-🎻 + +🚥 👆 🚫 😰 ⏮️ `f"userprofile::{username}"`, ⚫️ 🐍 "Ⓜ-🎻". + +🙆 🔢 👈 🚮 🔘 `{}` Ⓜ-🎻 🔜 ↔ / 💉 🎻. + +### `dict` 🏗 + +🚥 👆 🚫 😰 ⏮️ `UserInDB(**result.value)`, ⚫️ ⚙️ `dict` "🏗". + +⚫️ 🔜 ✊ `dict` `result.value`, & ✊ 🔠 🚮 🔑 & 💲 & 🚶‍♀️ 👫 🔑-💲 `UserInDB` 🇨🇻 ❌. + +, 🚥 `dict` 🔌: + +```Python +{ + "username": "johndoe", + "hashed_password": "some_hash", +} +``` + +⚫️ 🔜 🚶‍♀️ `UserInDB` : + +```Python +UserInDB(username="johndoe", hashed_password="some_hash") +``` + +## ✍ 👆 **FastAPI** 📟 + +### ✍ `FastAPI` 📱 + +```Python hl_lines="46" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +### ✍ *➡ 🛠️ 🔢* + +👆 📟 🤙 🗄 & 👥 🚫 ⚙️ 🥼 🐍 await 🐕‍🦺, 👥 🔜 📣 👆 🔢 ⏮️ 😐 `def` ↩️ `async def`. + +, 🗄 👍 🚫 ⚙️ 👁 `Bucket` 🎚 💗 "🧵Ⓜ",, 👥 💪 🤚 🥡 🔗 & 🚶‍♀️ ⚫️ 👆 🚙 🔢: + +```Python hl_lines="49-53" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## 🌃 + +👆 💪 🛠️ 🙆 🥉 🥳 ☁ 💽, ⚙️ 👫 🐩 📦. + +🎏 ✔ 🙆 🎏 🔢 🧰, ⚙️ ⚖️ 🛠️. diff --git a/docs/em/docs/advanced/openapi-callbacks.md b/docs/em/docs/advanced/openapi-callbacks.md new file mode 100644 index 0000000000..630b75ed29 --- /dev/null +++ b/docs/em/docs/advanced/openapi-callbacks.md @@ -0,0 +1,179 @@ +# 🗄 ⏲ + +👆 💪 ✍ 🛠️ ⏮️ *➡ 🛠️* 👈 💪 ⏲ 📨 *🔢 🛠️* ✍ 👱 🙆 (🎲 🎏 👩‍💻 👈 🔜 *⚙️* 👆 🛠️). + +🛠️ 👈 🔨 🕐❔ 👆 🛠️ 📱 🤙 *🔢 🛠️* 📛 "⏲". ↩️ 🖥 👈 🔢 👩‍💻 ✍ 📨 📨 👆 🛠️ & ⤴️ 👆 🛠️ *🤙 🔙*, 📨 📨 *🔢 🛠️* (👈 🎲 ✍ 🎏 👩‍💻). + +👉 💼, 👆 💪 💚 📄 ❔ 👈 🔢 🛠️ *🔜* 👀 💖. ⚫️❔ *➡ 🛠️* ⚫️ 🔜 ✔️, ⚫️❔ 💪 ⚫️ 🔜 ⌛, ⚫️❔ 📨 ⚫️ 🔜 📨, ♒️. + +## 📱 ⏮️ ⏲ + +➡️ 👀 🌐 👉 ⏮️ 🖼. + +🌈 👆 🛠️ 📱 👈 ✔ 🏗 🧾. + +👉 🧾 🔜 ✔️ `id`, `title` (📦), `customer`, & `total`. + +👩‍💻 👆 🛠️ (🔢 👩‍💻) 🔜 ✍ 🧾 👆 🛠️ ⏮️ 🏤 📨. + +⤴️ 👆 🛠️ 🔜 (➡️ 🌈): + +* 📨 🧾 🕴 🔢 👩‍💻. +* 📈 💸. +* 📨 📨 🔙 🛠️ 👩‍💻 (🔢 👩‍💻). + * 👉 🔜 🔨 📨 🏤 📨 (⚪️➡️ *👆 🛠️*) *🔢 🛠️* 🚚 👈 🔢 👩‍💻 (👉 "⏲"). + +## 😐 **FastAPI** 📱 + +➡️ 🥇 👀 ❔ 😐 🛠️ 📱 🔜 👀 💖 ⏭ ❎ ⏲. + +⚫️ 🔜 ✔️ *➡ 🛠️* 👈 🔜 📨 `Invoice` 💪, & 🔢 🔢 `callback_url` 👈 🔜 🔌 📛 ⏲. + +👉 🍕 📶 😐, 🌅 📟 🎲 ⏪ 😰 👆: + +```Python hl_lines="9-13 36-53" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +!!! tip + `callback_url` 🔢 🔢 ⚙️ Pydantic 📛 🆎. + +🕴 🆕 👜 `callbacks=messages_callback_router.routes` ❌ *➡ 🛠️ 👨‍🎨*. 👥 🔜 👀 ⚫️❔ 👈 ⏭. + +## 🔬 ⏲ + +☑ ⏲ 📟 🔜 🪀 🙇 🔛 👆 👍 🛠️ 📱. + +& ⚫️ 🔜 🎲 🪀 📚 ⚪️➡️ 1️⃣ 📱 ⏭. + +⚫️ 💪 1️⃣ ⚖️ 2️⃣ ⏸ 📟, 💖: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +✋️ 🎲 🏆 ⚠ 🍕 ⏲ ⚒ 💭 👈 👆 🛠️ 👩‍💻 (🔢 👩‍💻) 🛠️ *🔢 🛠️* ☑, 🛄 💽 👈 *👆 🛠️* 🔜 📨 📨 💪 ⏲, ♒️. + +, ⚫️❔ 👥 🔜 ⏭ 🚮 📟 📄 ❔ 👈 *🔢 🛠️* 🔜 👀 💖 📨 ⏲ ⚪️➡️ *👆 🛠️*. + +👈 🧾 🔜 🎦 🆙 🦁 🎚 `/docs` 👆 🛠️, & ⚫️ 🔜 ➡️ 🔢 👩‍💻 💭 ❔ 🏗 *🔢 🛠️*. + +👉 🖼 🚫 🛠️ ⏲ ⚫️ (👈 💪 ⏸ 📟), 🕴 🧾 🍕. + +!!! tip + ☑ ⏲ 🇺🇸🔍 📨. + + 🕐❔ 🛠️ ⏲ 👆, 👆 💪 ⚙️ 🕳 💖 🇸🇲 ⚖️ 📨. + +## ✍ ⏲ 🧾 📟 + +👉 📟 🏆 🚫 🛠️ 👆 📱, 👥 🕴 💪 ⚫️ *📄* ❔ 👈 *🔢 🛠️* 🔜 👀 💖. + +✋️, 👆 ⏪ 💭 ❔ 💪 ✍ 🏧 🧾 🛠️ ⏮️ **FastAPI**. + +👥 🔜 ⚙️ 👈 🎏 💡 📄 ❔ *🔢 🛠️* 🔜 👀 💖... 🏗 *➡ 🛠️(Ⓜ)* 👈 🔢 🛠️ 🔜 🛠️ (🕐 👆 🛠️ 🔜 🤙). + +!!! tip + 🕐❔ ✍ 📟 📄 ⏲, ⚫️ 💪 ⚠ 🌈 👈 👆 👈 *🔢 👩‍💻*. & 👈 👆 ⏳ 🛠️ *🔢 🛠️*, 🚫 *👆 🛠️*. + + 🍕 🛠️ 👉 ☝ 🎑 ( *🔢 👩‍💻*) 💪 ℹ 👆 💭 💖 ⚫️ 🌅 ⭐ 🌐❔ 🚮 🔢, Pydantic 🏷 💪, 📨, ♒️. 👈 *🔢 🛠️*. + +### ✍ ⏲ `APIRouter` + +🥇 ✍ 🆕 `APIRouter` 👈 🔜 🔌 1️⃣ ⚖️ 🌅 ⏲. + +```Python hl_lines="3 25" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +### ✍ ⏲ *➡ 🛠️* + +✍ ⏲ *➡ 🛠️* ⚙️ 🎏 `APIRouter` 👆 ✍ 🔛. + +⚫️ 🔜 👀 💖 😐 FastAPI *➡ 🛠️*: + +* ⚫️ 🔜 🎲 ✔️ 📄 💪 ⚫️ 🔜 📨, ✅ `body: InvoiceEvent`. +* & ⚫️ 💪 ✔️ 📄 📨 ⚫️ 🔜 📨, ✅ `response_model=InvoiceEventReceived`. + +```Python hl_lines="16-18 21-22 28-32" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +📤 2️⃣ 👑 🔺 ⚪️➡️ 😐 *➡ 🛠️*: + +* ⚫️ 🚫 💪 ✔️ 🙆 ☑ 📟, ↩️ 👆 📱 🔜 🙅 🤙 👉 📟. ⚫️ 🕴 ⚙️ 📄 *🔢 🛠️*. , 🔢 💪 ✔️ `pass`. +* *➡* 💪 🔌 🗄 3️⃣ 🧬 (👀 🌖 🔛) 🌐❔ ⚫️ 💪 ⚙️ 🔢 ⏮️ 🔢 & 🍕 ⏮️ 📨 📨 *👆 🛠️*. + +### ⏲ ➡ 🧬 + +⏲ *➡* 💪 ✔️ 🗄 3️⃣ 🧬 👈 💪 🔌 🍕 ⏮️ 📨 📨 *👆 🛠️*. + +👉 💼, ⚫️ `str`: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +, 🚥 👆 🛠️ 👩‍💻 (🔢 👩‍💻) 📨 📨 *👆 🛠️* : + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +⏮️ 🎻 💪: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +⤴️ *👆 🛠️* 🔜 🛠️ 🧾, & ☝ ⏪, 📨 ⏲ 📨 `callback_url` ( *🔢 🛠️*): + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +⏮️ 🎻 💪 ⚗ 🕳 💖: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +& ⚫️ 🔜 ⌛ 📨 ⚪️➡️ 👈 *🔢 🛠️* ⏮️ 🎻 💪 💖: + +```JSON +{ + "ok": true +} +``` + +!!! tip + 👀 ❔ ⏲ 📛 ⚙️ 🔌 📛 📨 🔢 🔢 `callback_url` (`https://www.external.org/events`) & 🧾 `id` ⚪️➡️ 🔘 🎻 💪 (`2expen51ve`). + +### 🚮 ⏲ 📻 + +👉 ☝ 👆 ✔️ *⏲ ➡ 🛠️(Ⓜ)* 💪 (1️⃣(Ⓜ) 👈 *🔢 👩‍💻* 🔜 🛠️ *🔢 🛠️*) ⏲ 📻 👆 ✍ 🔛. + +🔜 ⚙️ 🔢 `callbacks` *👆 🛠️ ➡ 🛠️ 👨‍🎨* 🚶‍♀️ 🔢 `.routes` (👈 🤙 `list` 🛣/*➡ 🛠️*) ⚪️➡️ 👈 ⏲ 📻: + +```Python hl_lines="35" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +!!! tip + 👀 👈 👆 🚫 🚶‍♀️ 📻 ⚫️ (`invoices_callback_router`) `callback=`, ✋️ 🔢 `.routes`, `invoices_callback_router.routes`. + +### ✅ 🩺 + +🔜 👆 💪 ▶️ 👆 📱 ⏮️ Uvicorn & 🚶 http://127.0.0.1:8000/docs. + +👆 🔜 👀 👆 🩺 ✅ "⏲" 📄 👆 *➡ 🛠️* 👈 🎦 ❔ *🔢 🛠️* 🔜 👀 💖: + + diff --git a/docs/em/docs/advanced/path-operation-advanced-configuration.md b/docs/em/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 0000000000..ec72318706 --- /dev/null +++ b/docs/em/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,170 @@ +# ➡ 🛠️ 🏧 📳 + +## 🗄 { + +!!! warning + 🚥 👆 🚫 "🕴" 🗄, 👆 🎲 🚫 💪 👉. + +👆 💪 ⚒ 🗄 `operationId` ⚙️ 👆 *➡ 🛠️* ⏮️ 🔢 `operation_id`. + +👆 🔜 ✔️ ⚒ 💭 👈 ⚫️ 😍 🔠 🛠️. + +```Python hl_lines="6" +{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} +``` + +### ⚙️ *➡ 🛠️ 🔢* 📛 { + +🚥 👆 💚 ⚙️ 👆 🔗' 🔢 📛 `operationId`Ⓜ, 👆 💪 🔁 🤭 🌐 👫 & 🔐 🔠 *➡ 🛠️* `operation_id` ⚙️ 👫 `APIRoute.name`. + +👆 🔜 ⚫️ ⏮️ ❎ 🌐 👆 *➡ 🛠️*. + +```Python hl_lines="2 12-21 24" +{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} +``` + +!!! tip + 🚥 👆 ❎ 🤙 `app.openapi()`, 👆 🔜 ℹ `operationId`Ⓜ ⏭ 👈. + +!!! warning + 🚥 👆 👉, 👆 ✔️ ⚒ 💭 🔠 1️⃣ 👆 *➡ 🛠️ 🔢* ✔️ 😍 📛. + + 🚥 👫 🎏 🕹 (🐍 📁). + +## 🚫 ⚪️➡️ 🗄 + +🚫 *➡ 🛠️* ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚙️ 🔢 `include_in_schema` & ⚒ ⚫️ `False`: + +```Python hl_lines="6" +{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!} +``` + +## 🏧 📛 ⚪️➡️ #️⃣ + +👆 💪 📉 ⏸ ⚙️ ⚪️➡️ #️⃣ *➡ 🛠️ 🔢* 🗄. + +❎ `\f` (😖 "📨 🍼" 🦹) 🤕 **FastAPI** 🔁 🔢 ⚙️ 🗄 👉 ☝. + +⚫️ 🏆 🚫 🎦 🆙 🧾, ✋️ 🎏 🧰 (✅ 🐉) 🔜 💪 ⚙️ 🎂. + +```Python hl_lines="19-29" +{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} +``` + +## 🌖 📨 + +👆 🎲 ✔️ 👀 ❔ 📣 `response_model` & `status_code` *➡ 🛠️*. + +👈 🔬 🗃 🔃 👑 📨 *➡ 🛠️*. + +👆 💪 📣 🌖 📨 ⏮️ 👫 🏷, 👔 📟, ♒️. + +📤 🎂 📃 📥 🧾 🔃 ⚫️, 👆 💪 ✍ ⚫️ [🌖 📨 🗄](./additional-responses.md){.internal-link target=_blank}. + +## 🗄 ➕ + +🕐❔ 👆 📣 *➡ 🛠️* 👆 🈸, **FastAPI** 🔁 🏗 🔗 🗃 🔃 👈 *➡ 🛠️* 🔌 🗄 🔗. + +!!! note "📡 ℹ" + 🗄 🔧 ⚫️ 🤙 🛠️ 🎚. + +⚫️ ✔️ 🌐 ℹ 🔃 *➡ 🛠️* & ⚙️ 🏗 🏧 🧾. + +⚫️ 🔌 `tags`, `parameters`, `requestBody`, `responses`, ♒️. + +👉 *➡ 🛠️*-🎯 🗄 🔗 🛎 🏗 🔁 **FastAPI**, ✋️ 👆 💪 ↔ ⚫️. + +!!! tip + 👉 🔅 🎚 ↔ ☝. + + 🚥 👆 🕴 💪 📣 🌖 📨, 🌅 🏪 🌌 ⚫️ ⏮️ [🌖 📨 🗄](./additional-responses.md){.internal-link target=_blank}. + +👆 💪 ↔ 🗄 🔗 *➡ 🛠️* ⚙️ 🔢 `openapi_extra`. + +### 🗄 ↔ + +👉 `openapi_extra` 💪 👍, 🖼, 📣 [🗄 ↔](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): + +```Python hl_lines="6" +{!../../../docs_src/path_operation_advanced_configuration/tutorial005.py!} +``` + +🚥 👆 📂 🏧 🛠️ 🩺, 👆 ↔ 🔜 🎦 🆙 🔝 🎯 *➡ 🛠️*. + + + +& 🚥 👆 👀 📉 🗄 ( `/openapi.json` 👆 🛠️), 👆 🔜 👀 👆 ↔ 🍕 🎯 *➡ 🛠️* 💁‍♂️: + +```JSON hl_lines="22" +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### 🛃 🗄 *➡ 🛠️* 🔗 + +📖 `openapi_extra` 🔜 🙇 🔗 ⏮️ 🔁 🏗 🗄 🔗 *➡ 🛠️*. + +, 👆 💪 🚮 🌖 💽 🔁 🏗 🔗. + +🖼, 👆 💪 💭 ✍ & ✔ 📨 ⏮️ 👆 👍 📟, 🍵 ⚙️ 🏧 ⚒ FastAPI ⏮️ Pydantic, ✋️ 👆 💪 💚 🔬 📨 🗄 🔗. + +👆 💪 👈 ⏮️ `openapi_extra`: + +```Python hl_lines="20-37 39-40" +{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py!} +``` + +👉 🖼, 👥 🚫 📣 🙆 Pydantic 🏷. 👐, 📨 💪 🚫 🎻 🎻, ⚫️ ✍ 🔗 `bytes`, & 🔢 `magic_data_reader()` 🔜 🈚 🎻 ⚫️ 🌌. + +👐, 👥 💪 📣 📈 🔗 📨 💪. + +### 🛃 🗄 🎚 🆎 + +⚙️ 👉 🎏 🎱, 👆 💪 ⚙️ Pydantic 🏷 🔬 🎻 🔗 👈 ⤴️ 🔌 🛃 🗄 🔗 📄 *➡ 🛠️*. + +& 👆 💪 👉 🚥 💽 🆎 📨 🚫 🎻. + +🖼, 👉 🈸 👥 🚫 ⚙️ FastAPI 🛠️ 🛠️ ⚗ 🎻 🔗 ⚪️➡️ Pydantic 🏷 🚫 🏧 🔬 🎻. 👐, 👥 📣 📨 🎚 🆎 📁, 🚫 🎻: + +```Python hl_lines="17-22 24" +{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} +``` + +👐, 👐 👥 🚫 ⚙️ 🔢 🛠️ 🛠️, 👥 ⚙️ Pydantic 🏷 ❎ 🏗 🎻 🔗 💽 👈 👥 💚 📨 📁. + +⤴️ 👥 ⚙️ 📨 🔗, & ⚗ 💪 `bytes`. 👉 ⛓ 👈 FastAPI 🏆 🚫 🔄 🎻 📨 🚀 🎻. + +& ⤴️ 👆 📟, 👥 🎻 👈 📁 🎚 🔗, & ⤴️ 👥 🔄 ⚙️ 🎏 Pydantic 🏷 ✔ 📁 🎚: + +```Python hl_lines="26-33" +{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} +``` + +!!! tip + 📥 👥 🏤-⚙️ 🎏 Pydantic 🏷. + + ✋️ 🎏 🌌, 👥 💪 ✔️ ✔ ⚫️ 🎏 🌌. diff --git a/docs/em/docs/advanced/response-change-status-code.md b/docs/em/docs/advanced/response-change-status-code.md new file mode 100644 index 0000000000..156efcc16a --- /dev/null +++ b/docs/em/docs/advanced/response-change-status-code.md @@ -0,0 +1,33 @@ +# 📨 - 🔀 👔 📟 + +👆 🎲 ✍ ⏭ 👈 👆 💪 ⚒ 🔢 [📨 👔 📟](../tutorial/response-status-code.md){.internal-link target=_blank}. + +✋️ 💼 👆 💪 📨 🎏 👔 📟 🌘 🔢. + +## ⚙️ 💼 + +🖼, 🌈 👈 👆 💚 📨 🇺🇸🔍 👔 📟 "👌" `200` 🔢. + +✋️ 🚥 💽 🚫 🔀, 👆 💚 ✍ ⚫️, & 📨 🇺🇸🔍 👔 📟 "✍" `201`. + +✋️ 👆 💚 💪 ⛽ & 🗜 💽 👆 📨 ⏮️ `response_model`. + +📚 💼, 👆 💪 ⚙️ `Response` 🔢. + +## ⚙️ `Response` 🔢 + +👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢* (👆 💪 🍪 & 🎚). + +& ⤴️ 👆 💪 ⚒ `status_code` 👈 *🔀* 📨 🎚. + +```Python hl_lines="1 9 12" +{!../../../docs_src/response_change_status_code/tutorial001.py!} +``` + +& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). + +& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨. + +**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 👔 📟 (🍪 & 🎚), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`. + +👆 💪 📣 `Response` 🔢 🔗, & ⚒ 👔 📟 👫. ✋️ ✔️ 🤯 👈 🏁 1️⃣ ⚒ 🔜 🏆. diff --git a/docs/em/docs/advanced/response-cookies.md b/docs/em/docs/advanced/response-cookies.md new file mode 100644 index 0000000000..23fffe1ddc --- /dev/null +++ b/docs/em/docs/advanced/response-cookies.md @@ -0,0 +1,49 @@ +# 📨 🍪 + +## ⚙️ `Response` 🔢 + +👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢*. + +& ⤴️ 👆 💪 ⚒ 🍪 👈 *🔀* 📨 🎚. + +```Python hl_lines="1 8-9" +{!../../../docs_src/response_cookies/tutorial002.py!} +``` + +& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). + +& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨. + +**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 🍪 (🎚 & 👔 📟), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`. + +👆 💪 📣 `Response` 🔢 🔗, & ⚒ 🍪 (& 🎚) 👫. + +## 📨 `Response` 🔗 + +👆 💪 ✍ 🍪 🕐❔ 🛬 `Response` 🔗 👆 📟. + +👈, 👆 💪 ✍ 📨 🔬 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}. + +⤴️ ⚒ 🍪 ⚫️, & ⤴️ 📨 ⚫️: + +```Python hl_lines="10-12" +{!../../../docs_src/response_cookies/tutorial001.py!} +``` + +!!! tip + ✔️ 🤯 👈 🚥 👆 📨 📨 🔗 ↩️ ⚙️ `Response` 🔢, FastAPI 🔜 📨 ⚫️ 🔗. + + , 👆 🔜 ✔️ ⚒ 💭 👆 💽 ☑ 🆎. 🤶 Ⓜ. ⚫️ 🔗 ⏮️ 🎻, 🚥 👆 🛬 `JSONResponse`. + + & 👈 👆 🚫 📨 🙆 📊 👈 🔜 ✔️ ⛽ `response_model`. + +### 🌅 ℹ + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. + + & `Response` 💪 ⚙️ 🛎 ⚒ 🎚 & 🍪, **FastAPI** 🚚 ⚫️ `fastapi.Response`. + +👀 🌐 💪 🔢 & 🎛, ✅ 🧾 💃. diff --git a/docs/em/docs/advanced/response-directly.md b/docs/em/docs/advanced/response-directly.md new file mode 100644 index 0000000000..ba09734fb3 --- /dev/null +++ b/docs/em/docs/advanced/response-directly.md @@ -0,0 +1,63 @@ +# 📨 📨 🔗 + +🕐❔ 👆 ✍ **FastAPI** *➡ 🛠️* 👆 💪 🛎 📨 🙆 📊 ⚪️➡️ ⚫️: `dict`, `list`, Pydantic 🏷, 💽 🏷, ♒️. + +🔢, **FastAPI** 🔜 🔁 🗜 👈 📨 💲 🎻 ⚙️ `jsonable_encoder` 🔬 [🎻 🔗 🔢](../tutorial/encoder.md){.internal-link target=_blank}. + +⤴️, ⛅ 🎑, ⚫️ 🔜 🚮 👈 🎻-🔗 💽 (✅ `dict`) 🔘 `JSONResponse` 👈 🔜 ⚙️ 📨 📨 👩‍💻. + +✋️ 👆 💪 📨 `JSONResponse` 🔗 ⚪️➡️ 👆 *➡ 🛠️*. + +⚫️ 💪 ⚠, 🖼, 📨 🛃 🎚 ⚖️ 🍪. + +## 📨 `Response` + +👐, 👆 💪 📨 🙆 `Response` ⚖️ 🙆 🎧-🎓 ⚫️. + +!!! tip + `JSONResponse` ⚫️ 🎧-🎓 `Response`. + +& 🕐❔ 👆 📨 `Response`, **FastAPI** 🔜 🚶‍♀️ ⚫️ 🔗. + +⚫️ 🏆 🚫 🙆 💽 🛠️ ⏮️ Pydantic 🏷, ⚫️ 🏆 🚫 🗜 🎚 🙆 🆎, ♒️. + +👉 🤝 👆 📚 💪. 👆 💪 📨 🙆 📊 🆎, 🔐 🙆 💽 📄 ⚖️ 🔬, ♒️. + +## ⚙️ `jsonable_encoder` `Response` + +↩️ **FastAPI** 🚫 🙆 🔀 `Response` 👆 📨, 👆 ✔️ ⚒ 💭 ⚫️ 🎚 🔜 ⚫️. + +🖼, 👆 🚫🔜 🚮 Pydantic 🏷 `JSONResponse` 🍵 🥇 🏭 ⚫️ `dict` ⏮️ 🌐 📊 🆎 (💖 `datetime`, `UUID`, ♒️) 🗜 🎻-🔗 🆎. + +📚 💼, 👆 💪 ⚙️ `jsonable_encoder` 🗜 👆 📊 ⏭ 🚶‍♀️ ⚫️ 📨: + +```Python hl_lines="6-7 21-22" +{!../../../docs_src/response_directly/tutorial001.py!} +``` + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.responses import JSONResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. + +## 🛬 🛃 `Response` + +🖼 🔛 🎦 🌐 🍕 👆 💪, ✋️ ⚫️ 🚫 📶 ⚠, 👆 💪 ✔️ 📨 `item` 🔗, & **FastAPI** 🔜 🚮 ⚫️ `JSONResponse` 👆, 🏭 ⚫️ `dict`, ♒️. 🌐 👈 🔢. + +🔜, ➡️ 👀 ❔ 👆 💪 ⚙️ 👈 📨 🛃 📨. + +➡️ 💬 👈 👆 💚 📨 📂 📨. + +👆 💪 🚮 👆 📂 🎚 🎻, 🚮 ⚫️ `Response`, & 📨 ⚫️: + +```Python hl_lines="1 18" +{!../../../docs_src/response_directly/tutorial002.py!} +``` + +## 🗒 + +🕐❔ 👆 📨 `Response` 🔗 🚮 📊 🚫 ✔, 🗜 (🎻), 🚫 📄 🔁. + +✋️ 👆 💪 📄 ⚫️ 🔬 [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}. + +👆 💪 👀 ⏪ 📄 ❔ ⚙️/📣 👉 🛃 `Response`Ⓜ ⏪ ✔️ 🏧 💽 🛠️, 🧾, ♒️. diff --git a/docs/em/docs/advanced/response-headers.md b/docs/em/docs/advanced/response-headers.md new file mode 100644 index 0000000000..de798982a9 --- /dev/null +++ b/docs/em/docs/advanced/response-headers.md @@ -0,0 +1,42 @@ +# 📨 🎚 + +## ⚙️ `Response` 🔢 + +👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢* (👆 💪 🍪). + +& ⤴️ 👆 💪 ⚒ 🎚 👈 *🔀* 📨 🎚. + +```Python hl_lines="1 7-8" +{!../../../docs_src/response_headers/tutorial002.py!} +``` + +& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). + +& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨. + +**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 🎚 (🍪 & 👔 📟), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`. + +👆 💪 📣 `Response` 🔢 🔗, & ⚒ 🎚 (& 🍪) 👫. + +## 📨 `Response` 🔗 + +👆 💪 🚮 🎚 🕐❔ 👆 📨 `Response` 🔗. + +✍ 📨 🔬 [📨 📨 🔗](response-directly.md){.internal-link target=_blank} & 🚶‍♀️ 🎚 🌖 🔢: + +```Python hl_lines="10-12" +{!../../../docs_src/response_headers/tutorial001.py!} +``` + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. + + & `Response` 💪 ⚙️ 🛎 ⚒ 🎚 & 🍪, **FastAPI** 🚚 ⚫️ `fastapi.Response`. + +## 🛃 🎚 + +✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡. + +✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 (✍ 🌅 [⚜ (✖️-🇨🇳 ℹ 🤝)](../tutorial/cors.md){.internal-link target=_blank}), ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺. diff --git a/docs/em/docs/advanced/security/http-basic-auth.md b/docs/em/docs/advanced/security/http-basic-auth.md new file mode 100644 index 0000000000..33470a7268 --- /dev/null +++ b/docs/em/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,113 @@ +# 🇺🇸🔍 🔰 🔐 + +🙅 💼, 👆 💪 ⚙️ 🇺🇸🔍 🔰 🔐. + +🇺🇸🔍 🔰 🔐, 🈸 ⌛ 🎚 👈 🔌 🆔 & 🔐. + +🚥 ⚫️ 🚫 📨 ⚫️, ⚫️ 📨 🇺🇸🔍 4️⃣0️⃣1️⃣ "⛔" ❌. + +& 📨 🎚 `WWW-Authenticate` ⏮️ 💲 `Basic`, & 📦 `realm` 🔢. + +👈 💬 🖥 🎦 🛠️ 📋 🆔 & 🔐. + +⤴️, 🕐❔ 👆 🆎 👈 🆔 & 🔐, 🖥 📨 👫 🎚 🔁. + +## 🙅 🇺🇸🔍 🔰 🔐 + +* 🗄 `HTTPBasic` & `HTTPBasicCredentials`. +* ✍ "`security` ⚖" ⚙️ `HTTPBasic`. +* ⚙️ 👈 `security` ⏮️ 🔗 👆 *➡ 🛠️*. +* ⚫️ 📨 🎚 🆎 `HTTPBasicCredentials`: + * ⚫️ 🔌 `username` & `password` 📨. + +```Python hl_lines="2 6 10" +{!../../../docs_src/security/tutorial006.py!} +``` + +🕐❔ 👆 🔄 📂 📛 🥇 🕰 (⚖️ 🖊 "🛠️" 🔼 🩺) 🖥 🔜 💭 👆 👆 🆔 & 🔐: + + + +## ✅ 🆔 + +📥 🌅 🏁 🖼. + +⚙️ 🔗 ✅ 🚥 🆔 & 🔐 ☑. + +👉, ⚙️ 🐍 🐩 🕹 `secrets` ✅ 🆔 & 🔐. + +`secrets.compare_digest()` 💪 ✊ `bytes` ⚖️ `str` 👈 🕴 🔌 🔠 🦹 (🕐 🇪🇸), 👉 ⛓ ⚫️ 🚫🔜 👷 ⏮️ 🦹 💖 `á`, `Sebastián`. + +🍵 👈, 👥 🥇 🗜 `username` & `password` `bytes` 🔢 👫 ⏮️ 🔠-8️⃣. + +⤴️ 👥 💪 ⚙️ `secrets.compare_digest()` 🚚 👈 `credentials.username` `"stanleyjobson"`, & 👈 `credentials.password` `"swordfish"`. + +```Python hl_lines="1 11-21" +{!../../../docs_src/security/tutorial007.py!} +``` + +👉 🔜 🎏: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +✋️ ⚙️ `secrets.compare_digest()` ⚫️ 🔜 🔐 🛡 🆎 👊 🤙 "🕰 👊". + +### ⏲ 👊 + +✋️ ⚫️❔ "⏲ 👊"❓ + +➡️ 🌈 👊 🔄 💭 🆔 & 🔐. + +& 👫 📨 📨 ⏮️ 🆔 `johndoe` & 🔐 `love123`. + +⤴️ 🐍 📟 👆 🈸 🔜 🌓 🕳 💖: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +✋️ ▶️️ 🙍 🐍 🔬 🥇 `j` `johndoe` 🥇 `s` `stanleyjobson`, ⚫️ 🔜 📨 `False`, ↩️ ⚫️ ⏪ 💭 👈 📚 2️⃣ 🎻 🚫 🎏, 💭 👈 "📤 🙅‍♂ 💪 🗑 🌅 📊 ⚖ 🎂 🔤". & 👆 🈸 🔜 💬 "❌ 👩‍💻 ⚖️ 🔐". + +✋️ ⤴️ 👊 🔄 ⏮️ 🆔 `stanleyjobsox` & 🔐 `love123`. + +& 👆 🈸 📟 🔨 🕳 💖: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +🐍 🔜 ✔️ 🔬 🎂 `stanleyjobso` 👯‍♂️ `stanleyjobsox` & `stanleyjobson` ⏭ 🤔 👈 👯‍♂️ 🎻 🚫 🎏. ⚫️ 🔜 ✊ ➕ ⏲ 📨 🔙 "❌ 👩‍💻 ⚖️ 🔐". + +#### 🕰 ❔ ℹ 👊 + +👈 ☝, 👀 👈 💽 ✊ ⏲ 📏 📨 "❌ 👩‍💻 ⚖️ 🔐" 📨, 👊 🔜 💭 👈 👫 🤚 _🕳_ ▶️️, ▶️ 🔤 ▶️️. + +& ⤴️ 👫 💪 🔄 🔄 🤔 👈 ⚫️ 🎲 🕳 🌖 🎏 `stanleyjobsox` 🌘 `johndoe`. + +#### "🕴" 👊 + +↗️, 👊 🔜 🚫 🔄 🌐 👉 ✋, 👫 🔜 ✍ 📋 ⚫️, 🎲 ⏮️ 💯 ⚖️ 💯 💯 📍 🥈. & 🔜 🤚 1️⃣ ➕ ☑ 🔤 🕰. + +✋️ 🔨 👈, ⏲ ⚖️ 📆 👊 🔜 ✔️ 💭 ☑ 🆔 & 🔐, ⏮️ "ℹ" 👆 🈸, ⚙️ 🕰 ✊ ❔. + +#### 🔧 ⚫️ ⏮️ `secrets.compare_digest()` + +✋️ 👆 📟 👥 🤙 ⚙️ `secrets.compare_digest()`. + +📏, ⚫️ 🔜 ✊ 🎏 🕰 🔬 `stanleyjobsox` `stanleyjobson` 🌘 ⚫️ ✊ 🔬 `johndoe` `stanleyjobson`. & 🎏 🔐. + +👈 🌌, ⚙️ `secrets.compare_digest()` 👆 🈸 📟, ⚫️ 🔜 🔒 🛡 👉 🎂 ↔ 💂‍♂ 👊. + +### 📨 ❌ + +⏮️ 🔍 👈 🎓 ❌, 📨 `HTTPException` ⏮️ 👔 📟 4️⃣0️⃣1️⃣ (🎏 📨 🕐❔ 🙅‍♂ 🎓 🚚) & 🚮 🎚 `WWW-Authenticate` ⚒ 🖥 🎦 💳 📋 🔄: + +```Python hl_lines="23-27" +{!../../../docs_src/security/tutorial007.py!} +``` diff --git a/docs/em/docs/advanced/security/index.md b/docs/em/docs/advanced/security/index.md new file mode 100644 index 0000000000..f2bb66df46 --- /dev/null +++ b/docs/em/docs/advanced/security/index.md @@ -0,0 +1,16 @@ +# 🏧 💂‍♂ + +## 🌖 ⚒ + +📤 ➕ ⚒ 🍵 💂‍♂ ↖️ ⚪️➡️ 🕐 📔 [🔰 - 👩‍💻 🦮: 💂‍♂](../../tutorial/security/){.internal-link target=_blank}. + +!!! tip + ⏭ 📄 **🚫 🎯 "🏧"**. + + & ⚫️ 💪 👈 👆 ⚙️ 💼, ⚗ 1️⃣ 👫. + +## ✍ 🔰 🥇 + +⏭ 📄 🤔 👆 ⏪ ✍ 👑 [🔰 - 👩‍💻 🦮: 💂‍♂](../../tutorial/security/){.internal-link target=_blank}. + +👫 🌐 ⚓️ 🔛 🎏 🔧, ✋️ ✔ ➕ 🛠️. diff --git a/docs/em/docs/advanced/security/oauth2-scopes.md b/docs/em/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 0000000000..a4684352cc --- /dev/null +++ b/docs/em/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,269 @@ +# Oauth2️⃣ ↔ + +👆 💪 ⚙️ Oauth2️⃣ ↔ 🔗 ⏮️ **FastAPI**, 👫 🛠️ 👷 💎. + +👉 🔜 ✔ 👆 ✔️ 🌖 👌-🧽 ✔ ⚙️, 📄 Oauth2️⃣ 🐩, 🛠️ 🔘 👆 🗄 🈸 (& 🛠️ 🩺). + +Oauth2️⃣ ⏮️ ↔ 🛠️ ⚙️ 📚 🦏 🤝 🐕‍🦺, 💖 👱📔, 🇺🇸🔍, 📂, 🤸‍♂, 👱📔, ♒️. 👫 ⚙️ ⚫️ 🚚 🎯 ✔ 👩‍💻 & 🈸. + +🔠 🕰 👆 "🕹 ⏮️" 👱📔, 🇺🇸🔍, 📂, 🤸‍♂, 👱📔, 👈 🈸 ⚙️ Oauth2️⃣ ⏮️ ↔. + +👉 📄 👆 🔜 👀 ❔ 🛠️ 🤝 & ✔ ⏮️ 🎏 Oauth2️⃣ ⏮️ ↔ 👆 **FastAPI** 🈸. + +!!! warning + 👉 🌅 ⚖️ 🌘 🏧 📄. 🚥 👆 ▶️, 👆 💪 🚶 ⚫️. + + 👆 🚫 🎯 💪 Oauth2️⃣ ↔, & 👆 💪 🍵 🤝 & ✔ 👐 👆 💚. + + ✋️ Oauth2️⃣ ⏮️ ↔ 💪 🎆 🛠️ 🔘 👆 🛠️ (⏮️ 🗄) & 👆 🛠️ 🩺. + + 👐, 👆 🛠️ 📚 ↔, ⚖️ 🙆 🎏 💂‍♂/✔ 📄, 👐 👆 💪, 👆 📟. + + 📚 💼, Oauth2️⃣ ⏮️ ↔ 💪 👹. + + ✋️ 🚥 👆 💭 👆 💪 ⚫️, ⚖️ 👆 😟, 🚧 👂. + +## Oauth2️⃣ ↔ & 🗄 + +Oauth2️⃣ 🔧 🔬 "↔" 📇 🎻 🎏 🚀. + +🎚 🔠 👉 🎻 💪 ✔️ 🙆 📁, ✋️ 🔜 🚫 🔌 🚀. + +👫 ↔ 🎨 "✔". + +🗄 (✅ 🛠️ 🩺), 👆 💪 🔬 "💂‍♂ ⚖". + +🕐❔ 1️⃣ 👫 💂‍♂ ⚖ ⚙️ Oauth2️⃣, 👆 💪 📣 & ⚙️ ↔. + +🔠 "↔" 🎻 (🍵 🚀). + +👫 🛎 ⚙️ 📣 🎯 💂‍♂ ✔, 🖼: + +* `users:read` ⚖️ `users:write` ⚠ 🖼. +* `instagram_basic` ⚙️ 👱📔 / 👱📔. +* `https://www.googleapis.com/auth/drive` ⚙️ 🇺🇸🔍. + +!!! info + Oauth2️⃣ "↔" 🎻 👈 📣 🎯 ✔ ✔. + + ⚫️ 🚫 🤔 🚥 ⚫️ ✔️ 🎏 🦹 💖 `:` ⚖️ 🚥 ⚫️ 📛. + + 👈 ℹ 🛠️ 🎯. + + Oauth2️⃣ 👫 🎻. + +## 🌐 🎑 + +🥇, ➡️ 🔜 👀 🍕 👈 🔀 ⚪️➡️ 🖼 👑 **🔰 - 👩‍💻 🦮** [Oauth2️⃣ ⏮️ 🔐 (& 🔁), 📨 ⏮️ 🥙 🤝](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. 🔜 ⚙️ Oauth2️⃣ ↔: + +```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 153" +{!../../../docs_src/security/tutorial005.py!} +``` + +🔜 ➡️ 📄 👈 🔀 🔁 🔁. + +## Oauth2️⃣ 💂‍♂ ⚖ + +🥇 🔀 👈 🔜 👥 📣 Oauth2️⃣ 💂‍♂ ⚖ ⏮️ 2️⃣ 💪 ↔, `me` & `items`. + +`scopes` 🔢 📨 `dict` ⏮️ 🔠 ↔ 🔑 & 📛 💲: + +```Python hl_lines="62-65" +{!../../../docs_src/security/tutorial005.py!} +``` + +↩️ 👥 🔜 📣 📚 ↔, 👫 🔜 🎦 🆙 🛠️ 🩺 🕐❔ 👆 🕹-/✔. + +& 👆 🔜 💪 🖊 ❔ ↔ 👆 💚 🤝 🔐: `me` & `items`. + +👉 🎏 🛠️ ⚙️ 🕐❔ 👆 🤝 ✔ ⏪ 🚨 ⏮️ 👱📔, 🇺🇸🔍, 📂, ♒️: + + + +## 🥙 🤝 ⏮️ ↔ + +🔜, 🔀 🤝 *➡ 🛠️* 📨 ↔ 📨. + +👥 ⚙️ 🎏 `OAuth2PasswordRequestForm`. ⚫️ 🔌 🏠 `scopes` ⏮️ `list` `str`, ⏮️ 🔠 ↔ ⚫️ 📨 📨. + +& 👥 📨 ↔ 🍕 🥙 🤝. + +!!! danger + 🦁, 📥 👥 ❎ ↔ 📨 🔗 🤝. + + ✋️ 👆 🈸, 💂‍♂, 👆 🔜 ⚒ 💭 👆 🕴 🚮 ↔ 👈 👩‍💻 🤙 💪 ✔️, ⚖️ 🕐 👆 ✔️ 🔁. + +```Python hl_lines="153" +{!../../../docs_src/security/tutorial005.py!} +``` + +## 📣 ↔ *➡ 🛠️* & 🔗 + +🔜 👥 📣 👈 *➡ 🛠️* `/users/me/items/` 🚚 ↔ `items`. + +👉, 👥 🗄 & ⚙️ `Security` ⚪️➡️ `fastapi`. + +👆 💪 ⚙️ `Security` 📣 🔗 (💖 `Depends`), ✋️ `Security` 📨 🔢 `scopes` ⏮️ 📇 ↔ (🎻). + +👉 💼, 👥 🚶‍♀️ 🔗 🔢 `get_current_active_user` `Security` (🎏 🌌 👥 🔜 ⏮️ `Depends`). + +✋️ 👥 🚶‍♀️ `list` ↔, 👉 💼 ⏮️ 1️⃣ ↔: `items` (⚫️ 💪 ✔️ 🌅). + +& 🔗 🔢 `get_current_active_user` 💪 📣 🎧-🔗, 🚫 🕴 ⏮️ `Depends` ✋️ ⏮️ `Security`. 📣 🚮 👍 🎧-🔗 🔢 (`get_current_user`), & 🌖 ↔ 📄. + +👉 💼, ⚫️ 🚚 ↔ `me` (⚫️ 💪 🚚 🌅 🌘 1️⃣ ↔). + +!!! note + 👆 🚫 🎯 💪 🚮 🎏 ↔ 🎏 🥉. + + 👥 🔨 ⚫️ 📥 🎦 ❔ **FastAPI** 🍵 ↔ 📣 🎏 🎚. + +```Python hl_lines="4 139 166" +{!../../../docs_src/security/tutorial005.py!} +``` + +!!! info "📡 ℹ" + `Security` 🤙 🏿 `Depends`, & ⚫️ ✔️ 1️⃣ ➕ 🔢 👈 👥 🔜 👀 ⏪. + + ✋️ ⚙️ `Security` ↩️ `Depends`, **FastAPI** 🔜 💭 👈 ⚫️ 💪 📣 💂‍♂ ↔, ⚙️ 👫 🔘, & 📄 🛠️ ⏮️ 🗄. + + ✋️ 🕐❔ 👆 🗄 `Query`, `Path`, `Depends`, `Security` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. + +## ⚙️ `SecurityScopes` + +🔜 ℹ 🔗 `get_current_user`. + +👉 1️⃣ ⚙️ 🔗 🔛. + +📥 👥 ⚙️ 🎏 Oauth2️⃣ ⚖ 👥 ✍ ⏭, 📣 ⚫️ 🔗: `oauth2_scheme`. + +↩️ 👉 🔗 🔢 🚫 ✔️ 🙆 ↔ 📄 ⚫️, 👥 💪 ⚙️ `Depends` ⏮️ `oauth2_scheme`, 👥 🚫 ✔️ ⚙️ `Security` 🕐❔ 👥 🚫 💪 ✔ 💂‍♂ ↔. + +👥 📣 🎁 🔢 🆎 `SecurityScopes`, 🗄 ⚪️➡️ `fastapi.security`. + +👉 `SecurityScopes` 🎓 🎏 `Request` (`Request` ⚙️ 🤚 📨 🎚 🔗). + +```Python hl_lines="8 105" +{!../../../docs_src/security/tutorial005.py!} +``` + +## ⚙️ `scopes` + +🔢 `security_scopes` 🔜 🆎 `SecurityScopes`. + +⚫️ 🔜 ✔️ 🏠 `scopes` ⏮️ 📇 ⚗ 🌐 ↔ ✔ ⚫️ & 🌐 🔗 👈 ⚙️ 👉 🎧-🔗. 👈 ⛓, 🌐 "⚓️"... 👉 💪 🔊 😨, ⚫️ 🔬 🔄 ⏪ 🔛. + +`security_scopes` 🎚 (🎓 `SecurityScopes`) 🚚 `scope_str` 🔢 ⏮️ 👁 🎻, 🔌 👈 ↔ 👽 🚀 (👥 🔜 ⚙️ ⚫️). + +👥 ✍ `HTTPException` 👈 👥 💪 🏤-⚙️ (`raise`) ⏪ 📚 ☝. + +👉 ⚠, 👥 🔌 ↔ 🚚 (🚥 🙆) 🎻 👽 🚀 (⚙️ `scope_str`). 👥 🚮 👈 🎻 ⚗ ↔ `WWW-Authenticate` 🎚 (👉 🍕 🔌). + +```Python hl_lines="105 107-115" +{!../../../docs_src/security/tutorial005.py!} +``` + +## ✔ `username` & 💽 💠 + +👥 ✔ 👈 👥 🤚 `username`, & ⚗ ↔. + +& ⤴️ 👥 ✔ 👈 📊 ⏮️ Pydantic 🏷 (✊ `ValidationError` ⚠), & 🚥 👥 🤚 ❌ 👂 🥙 🤝 ⚖️ ⚖ 📊 ⏮️ Pydantic, 👥 🤚 `HTTPException` 👥 ✍ ⏭. + +👈, 👥 ℹ Pydantic 🏷 `TokenData` ⏮️ 🆕 🏠 `scopes`. + +⚖ 📊 ⏮️ Pydantic 👥 💪 ⚒ 💭 👈 👥 ✔️, 🖼, ⚫️❔ `list` `str` ⏮️ ↔ & `str` ⏮️ `username`. + +↩️, 🖼, `dict`, ⚖️ 🕳 🙆, ⚫️ 💪 💔 🈸 ☝ ⏪, ⚒ ⚫️ 💂‍♂ ⚠. + +👥 ✔ 👈 👥 ✔️ 👩‍💻 ⏮️ 👈 🆔, & 🚥 🚫, 👥 🤚 👈 🎏 ⚠ 👥 ✍ ⏭. + +```Python hl_lines="46 116-127" +{!../../../docs_src/security/tutorial005.py!} +``` + +## ✔ `scopes` + +👥 🔜 ✔ 👈 🌐 ↔ ✔, 👉 🔗 & 🌐 ⚓️ (🔌 *➡ 🛠️*), 🔌 ↔ 🚚 🤝 📨, ⏪ 🤚 `HTTPException`. + +👉, 👥 ⚙️ `security_scopes.scopes`, 👈 🔌 `list` ⏮️ 🌐 👫 ↔ `str`. + +```Python hl_lines="128-134" +{!../../../docs_src/security/tutorial005.py!} +``` + +## 🔗 🌲 & ↔ + +➡️ 📄 🔄 👉 🔗 🌲 & ↔. + +`get_current_active_user` 🔗 ✔️ 🎧-🔗 🔛 `get_current_user`, ↔ `"me"` 📣 `get_current_active_user` 🔜 🔌 📇 ✔ ↔ `security_scopes.scopes` 🚶‍♀️ `get_current_user`. + +*➡ 🛠️* ⚫️ 📣 ↔, `"items"`, 👉 🔜 📇 `security_scopes.scopes` 🚶‍♀️ `get_current_user`. + +📥 ❔ 🔗 🔗 & ↔ 👀 💖: + +* *➡ 🛠️* `read_own_items` ✔️: + * ✔ ↔ `["items"]` ⏮️ 🔗: + * `get_current_active_user`: + * 🔗 🔢 `get_current_active_user` ✔️: + * ✔ ↔ `["me"]` ⏮️ 🔗: + * `get_current_user`: + * 🔗 🔢 `get_current_user` ✔️: + * 🙅‍♂ ↔ ✔ ⚫️. + * 🔗 ⚙️ `oauth2_scheme`. + * `security_scopes` 🔢 🆎 `SecurityScopes`: + * 👉 `security_scopes` 🔢 ✔️ 🏠 `scopes` ⏮️ `list` ⚗ 🌐 👫 ↔ 📣 🔛,: + * `security_scopes.scopes` 🔜 🔌 `["me", "items"]` *➡ 🛠️* `read_own_items`. + * `security_scopes.scopes` 🔜 🔌 `["me"]` *➡ 🛠️* `read_users_me`, ↩️ ⚫️ 📣 🔗 `get_current_active_user`. + * `security_scopes.scopes` 🔜 🔌 `[]` (🕳) *➡ 🛠️* `read_system_status`, ↩️ ⚫️ 🚫 📣 🙆 `Security` ⏮️ `scopes`, & 🚮 🔗, `get_current_user`, 🚫 📣 🙆 `scope` 👯‍♂️. + +!!! tip + ⚠ & "🎱" 👜 📥 👈 `get_current_user` 🔜 ✔️ 🎏 📇 `scopes` ✅ 🔠 *➡ 🛠️*. + + 🌐 ⚓️ 🔛 `scopes` 📣 🔠 *➡ 🛠️* & 🔠 🔗 🔗 🌲 👈 🎯 *➡ 🛠️*. + +## 🌖 ℹ 🔃 `SecurityScopes` + +👆 💪 ⚙️ `SecurityScopes` 🙆 ☝, & 💗 🥉, ⚫️ 🚫 ✔️ "🌱" 🔗. + +⚫️ 🔜 🕧 ✔️ 💂‍♂ ↔ 📣 ⏮️ `Security` 🔗 & 🌐 ⚓️ **👈 🎯** *➡ 🛠️* & **👈 🎯** 🔗 🌲. + +↩️ `SecurityScopes` 🔜 ✔️ 🌐 ↔ 📣 ⚓️, 👆 💪 ⚙️ ⚫️ ✔ 👈 🤝 ✔️ 🚚 ↔ 🇨🇫 🔗 🔢, & ⤴️ 📣 🎏 ↔ 📄 🎏 *➡ 🛠️*. + +👫 🔜 ✅ ➡ 🔠 *➡ 🛠️*. + +## ✅ ⚫️ + +🚥 👆 📂 🛠️ 🩺, 👆 💪 🔓 & ✔ ❔ ↔ 👆 💚 ✔. + + + +🚥 👆 🚫 🖊 🙆 ↔, 👆 🔜 "🔓", ✋️ 🕐❔ 👆 🔄 🔐 `/users/me/` ⚖️ `/users/me/items/` 👆 🔜 🤚 ❌ 💬 👈 👆 🚫 ✔️ 🥃 ✔. 👆 🔜 💪 🔐 `/status/`. + +& 🚥 👆 🖊 ↔ `me` ✋️ 🚫 ↔ `items`, 👆 🔜 💪 🔐 `/users/me/` ✋️ 🚫 `/users/me/items/`. + +👈 ⚫️❔ 🔜 🔨 🥉 🥳 🈸 👈 🔄 🔐 1️⃣ 👫 *➡ 🛠️* ⏮️ 🤝 🚚 👩‍💻, ⚓️ 🔛 ❔ 📚 ✔ 👩‍💻 🤝 🈸. + +## 🔃 🥉 🥳 🛠️ + +👉 🖼 👥 ⚙️ Oauth2️⃣ "🔐" 💧. + +👉 ☑ 🕐❔ 👥 🚨 👆 👍 🈸, 🎲 ⏮️ 👆 👍 🕸. + +↩️ 👥 💪 💙 ⚫️ 📨 `username` & `password`, 👥 🎛 ⚫️. + +✋️ 🚥 👆 🏗 Oauth2️⃣ 🈸 👈 🎏 🔜 🔗 (➡, 🚥 👆 🏗 🤝 🐕‍🦺 🌓 👱📔, 🇺🇸🔍, 📂, ♒️.) 👆 🔜 ⚙️ 1️⃣ 🎏 💧. + +🌅 ⚠ 🔑 💧. + +🏆 🔐 📟 💧, ✋️ 🌖 🏗 🛠️ ⚫️ 🚚 🌅 📶. ⚫️ 🌅 🏗, 📚 🐕‍🦺 🔚 🆙 ✔ 🔑 💧. + +!!! note + ⚫️ ⚠ 👈 🔠 🤝 🐕‍🦺 📛 👫 💧 🎏 🌌, ⚒ ⚫️ 🍕 👫 🏷. + + ✋️ 🔚, 👫 🛠️ 🎏 Oauth2️⃣ 🐩. + +**FastAPI** 🔌 🚙 🌐 👫 Oauth2️⃣ 🤝 💧 `fastapi.security.oauth2`. + +## `Security` 👨‍🎨 `dependencies` + +🎏 🌌 👆 💪 🔬 `list` `Depends` 👨‍🎨 `dependencies` 🔢 (🔬 [🔗 ➡ 🛠️ 👨‍🎨](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), 👆 💪 ⚙️ `Security` ⏮️ `scopes` 📤. diff --git a/docs/em/docs/advanced/settings.md b/docs/em/docs/advanced/settings.md new file mode 100644 index 0000000000..bc50bf755a --- /dev/null +++ b/docs/em/docs/advanced/settings.md @@ -0,0 +1,382 @@ +# ⚒ & 🌐 🔢 + +📚 💼 👆 🈸 💪 💪 🔢 ⚒ ⚖️ 📳, 🖼 ㊙ 🔑, 💽 🎓, 🎓 📧 🐕‍🦺, ♒️. + +🏆 👫 ⚒ 🔢 (💪 🔀), 💖 💽 📛. & 📚 💪 🚿, 💖 ㊙. + +👉 🤔 ⚫️ ⚠ 🚚 👫 🌐 🔢 👈 ✍ 🈸. + +## 🌐 🔢 + +!!! tip + 🚥 👆 ⏪ 💭 ⚫️❔ "🌐 🔢" & ❔ ⚙️ 👫, 💭 🆓 🚶 ⏭ 📄 🔛. + +🌐 🔢 (💭 "🇨🇻 {") 🔢 👈 🖖 🏞 🐍 📟, 🏃‍♂ ⚙️, & 💪 ✍ 👆 🐍 📟 (⚖️ 🎏 📋 👍). + +👆 💪 ✍ & ⚙️ 🌐 🔢 🐚, 🍵 💆‍♂ 🐍: + +=== "💾, 🇸🇻, 🚪 🎉" + +
+ + ```console + // You could create an env var MY_NAME with + $ export MY_NAME="Wade Wilson" + + // Then you could use it with other programs, like + $ echo "Hello $MY_NAME" + + Hello Wade Wilson + ``` + +
+ +=== "🚪 📋" + +
+ + ```console + // Create an env var MY_NAME + $ $Env:MY_NAME = "Wade Wilson" + + // Use it with other programs, like + $ echo "Hello $Env:MY_NAME" + + Hello Wade Wilson + ``` + +
+ +### ✍ 🇨🇻 {🐍 + +👆 💪 ✍ 🌐 🔢 🏞 🐍, 📶 (⚖️ ⏮️ 🙆 🎏 👩‍🔬), & ⤴️ ✍ 👫 🐍. + +🖼 👆 💪 ✔️ 📁 `main.py` ⏮️: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +!!! tip + 🥈 ❌ `os.getenv()` 🔢 💲 📨. + + 🚥 🚫 🚚, ⚫️ `None` 🔢, 📥 👥 🚚 `"World"` 🔢 💲 ⚙️. + +⤴️ 👆 💪 🤙 👈 🐍 📋: + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ export MY_NAME="Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +🌐 🔢 💪 ⚒ 🏞 📟, ✋️ 💪 ✍ 📟, & 🚫 ✔️ 🏪 (💕 `git`) ⏮️ 🎂 📁, ⚫️ ⚠ ⚙️ 👫 📳 ⚖️ ⚒. + +👆 💪 ✍ 🌐 🔢 🕴 🎯 📋 👼, 👈 🕴 💪 👈 📋, & 🕴 🚮 📐. + +👈, ✍ ⚫️ ▶️️ ⏭ 📋 ⚫️, 🔛 🎏 ⏸: + +
+ +```console +// Create an env var MY_NAME in line for this program call +$ MY_NAME="Wade Wilson" python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python + +// The env var no longer exists afterwards +$ python main.py + +Hello World from Python +``` + +
+ +!!! tip + 👆 💪 ✍ 🌅 🔃 ⚫️ 1️⃣2️⃣-⚖ 📱: 📁. + +### 🆎 & 🔬 + +👫 🌐 🔢 💪 🕴 🍵 ✍ 🎻, 👫 🔢 🐍 & ✔️ 🔗 ⏮️ 🎏 📋 & 🎂 ⚙️ (& ⏮️ 🎏 🏃‍♂ ⚙️, 💾, 🚪, 🇸🇻). + +👈 ⛓ 👈 🙆 💲 ✍ 🐍 ⚪️➡️ 🌐 🔢 🔜 `str`, & 🙆 🛠️ 🎏 🆎 ⚖️ 🔬 ✔️ 🔨 📟. + +## Pydantic `Settings` + +👐, Pydantic 🚚 👑 🚙 🍵 👫 ⚒ 👟 ⚪️➡️ 🌐 🔢 ⏮️ Pydantic: ⚒ 🧾. + +### ✍ `Settings` 🎚 + +🗄 `BaseSettings` ⚪️➡️ Pydantic & ✍ 🎧-🎓, 📶 🌅 💖 ⏮️ Pydantic 🏷. + +🎏 🌌 ⏮️ Pydantic 🏷, 👆 📣 🎓 🔢 ⏮️ 🆎 ✍, & 🎲 🔢 💲. + +👆 💪 ⚙️ 🌐 🎏 🔬 ⚒ & 🧰 👆 ⚙️ Pydantic 🏷, 💖 🎏 📊 🆎 & 🌖 🔬 ⏮️ `Field()`. + +```Python hl_lines="2 5-8 11" +{!../../../docs_src/settings/tutorial001.py!} +``` + +!!! tip + 🚥 👆 💚 🕳 ⏩ 📁 & 📋, 🚫 ⚙️ 👉 🖼, ⚙️ 🏁 1️⃣ 🔛. + +⤴️, 🕐❔ 👆 ✍ 👐 👈 `Settings` 🎓 (👉 💼, `settings` 🎚), Pydantic 🔜 ✍ 🌐 🔢 💼-😛 🌌,, ↖-💼 🔢 `APP_NAME` 🔜 ✍ 🔢 `app_name`. + +⏭ ⚫️ 🔜 🗜 & ✔ 💽. , 🕐❔ 👆 ⚙️ 👈 `settings` 🎚, 👆 🔜 ✔️ 📊 🆎 👆 📣 (✅ `items_per_user` 🔜 `int`). + +### ⚙️ `settings` + +⤴️ 👆 💪 ⚙️ 🆕 `settings` 🎚 👆 🈸: + +```Python hl_lines="18-20" +{!../../../docs_src/settings/tutorial001.py!} +``` + +### 🏃 💽 + +⏭, 👆 🔜 🏃 💽 🚶‍♀️ 📳 🌐 🔢, 🖼 👆 💪 ⚒ `ADMIN_EMAIL` & `APP_NAME` ⏮️: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +!!! tip + ⚒ 💗 🇨🇻 {👁 📋 🎏 👫 ⏮️ 🚀, & 🚮 👫 🌐 ⏭ 📋. + +& ⤴️ `admin_email` ⚒ 🔜 ⚒ `"deadpool@example.com"`. + +`app_name` 🔜 `"ChimichangApp"`. + +& `items_per_user` 🔜 🚧 🚮 🔢 💲 `50`. + +## ⚒ ➕1️⃣ 🕹 + +👆 💪 🚮 👈 ⚒ ➕1️⃣ 🕹 📁 👆 👀 [🦏 🈸 - 💗 📁](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +🖼, 👆 💪 ✔️ 📁 `config.py` ⏮️: + +```Python +{!../../../docs_src/settings/app01/config.py!} +``` + +& ⤴️ ⚙️ ⚫️ 📁 `main.py`: + +```Python hl_lines="3 11-13" +{!../../../docs_src/settings/app01/main.py!} +``` + +!!! tip + 👆 🔜 💪 📁 `__init__.py` 👆 👀 🔛 [🦏 🈸 - 💗 📁](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +## ⚒ 🔗 + +🍾 ⚫️ 5️⃣📆 ⚠ 🚚 ⚒ ⚪️➡️ 🔗, ↩️ ✔️ 🌐 🎚 ⏮️ `settings` 👈 ⚙️ 🌐. + +👉 💪 ✴️ ⚠ ⏮️ 🔬, ⚫️ 📶 ⏩ 🔐 🔗 ⏮️ 👆 👍 🛃 ⚒. + +### 📁 📁 + +👟 ⚪️➡️ ⏮️ 🖼, 👆 `config.py` 📁 💪 👀 💖: + +```Python hl_lines="10" +{!../../../docs_src/settings/app02/config.py!} +``` + +👀 👈 🔜 👥 🚫 ✍ 🔢 👐 `settings = Settings()`. + +### 👑 📱 📁 + +🔜 👥 ✍ 🔗 👈 📨 🆕 `config.Settings()`. + +```Python hl_lines="5 11-12" +{!../../../docs_src/settings/app02/main.py!} +``` + +!!! tip + 👥 🔜 🔬 `@lru_cache()` 🍖. + + 🔜 👆 💪 🤔 `get_settings()` 😐 🔢. + +& ⤴️ 👥 💪 🚚 ⚫️ ⚪️➡️ *➡ 🛠️ 🔢* 🔗 & ⚙️ ⚫️ 🙆 👥 💪 ⚫️. + +```Python hl_lines="16 18-20" +{!../../../docs_src/settings/app02/main.py!} +``` + +### ⚒ & 🔬 + +⤴️ ⚫️ 🔜 📶 ⏩ 🚚 🎏 ⚒ 🎚 ⏮️ 🔬 🏗 🔗 🔐 `get_settings`: + +```Python hl_lines="9-10 13 21" +{!../../../docs_src/settings/app02/test_main.py!} +``` + +🔗 🔐 👥 ⚒ 🆕 💲 `admin_email` 🕐❔ 🏗 🆕 `Settings` 🎚, & ⤴️ 👥 📨 👈 🆕 🎚. + +⤴️ 👥 💪 💯 👈 ⚫️ ⚙️. + +## 👂 `.env` 📁 + +🚥 👆 ✔️ 📚 ⚒ 👈 🎲 🔀 📚, 🎲 🎏 🌐, ⚫️ 5️⃣📆 ⚠ 🚮 👫 🔛 📁 & ⤴️ ✍ 👫 ⚪️➡️ ⚫️ 🚥 👫 🌐 🔢. + +👉 💡 ⚠ 🥃 👈 ⚫️ ✔️ 📛, 👫 🌐 🔢 🛎 🥉 📁 `.env`, & 📁 🤙 "🇨🇻". + +!!! tip + 📁 ▶️ ⏮️ ❣ (`.`) 🕵‍♂ 📁 🖥-💖 ⚙️, 💖 💾 & 🇸🇻. + + ✋️ 🇨🇻 📁 🚫 🤙 ✔️ ✔️ 👈 ☑ 📁. + +Pydantic ✔️ 🐕‍🦺 👂 ⚪️➡️ 👉 🆎 📁 ⚙️ 🔢 🗃. 👆 💪 ✍ 🌖 Pydantic ⚒: 🇨🇻 (.🇨🇻) 🐕‍🦺. + +!!! tip + 👉 👷, 👆 💪 `pip install python-dotenv`. + +### `.env` 📁 + +👆 💪 ✔️ `.env` 📁 ⏮️: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### ✍ ⚒ ⚪️➡️ `.env` + +& ⤴️ ℹ 👆 `config.py` ⏮️: + +```Python hl_lines="9-10" +{!../../../docs_src/settings/app03/config.py!} +``` + +📥 👥 ✍ 🎓 `Config` 🔘 👆 Pydantic `Settings` 🎓, & ⚒ `env_file` 📁 ⏮️ 🇨🇻 📁 👥 💚 ⚙️. + +!!! tip + `Config` 🎓 ⚙️ Pydantic 📳. 👆 💪 ✍ 🌖 Pydantic 🏷 📁 + +### 🏗 `Settings` 🕴 🕐 ⏮️ `lru_cache` + +👂 📁 ⚪️➡️ 💾 🛎 ⚠ (🐌) 🛠️, 👆 🎲 💚 ⚫️ 🕴 🕐 & ⤴️ 🏤-⚙️ 🎏 ⚒ 🎚, ↩️ 👂 ⚫️ 🔠 📨. + +✋️ 🔠 🕰 👥: + +```Python +Settings() +``` + +🆕 `Settings` 🎚 🔜 ✍, & 🏗 ⚫️ 🔜 ✍ `.env` 📁 🔄. + +🚥 🔗 🔢 💖: + +```Python +def get_settings(): + return Settings() +``` + +👥 🔜 ✍ 👈 🎚 🔠 📨, & 👥 🔜 👂 `.env` 📁 🔠 📨. 👶 👶 + +✋️ 👥 ⚙️ `@lru_cache()` 👨‍🎨 🔛 🔝, `Settings` 🎚 🔜 ✍ 🕴 🕐, 🥇 🕰 ⚫️ 🤙. 👶 👶 + +```Python hl_lines="1 10" +{!../../../docs_src/settings/app03/main.py!} +``` + +⤴️ 🙆 🏁 🤙 `get_settings()` 🔗 ⏭ 📨, ↩️ 🛠️ 🔗 📟 `get_settings()` & 🏗 🆕 `Settings` 🎚, ⚫️ 🔜 📨 🎏 🎚 👈 📨 🔛 🥇 🤙, 🔄 & 🔄. + +#### `lru_cache` 📡 ℹ + +`@lru_cache()` 🔀 🔢 ⚫️ 🎀 📨 🎏 💲 👈 📨 🥇 🕰, ↩️ 💻 ⚫️ 🔄, 🛠️ 📟 🔢 🔠 🕰. + +, 🔢 🔛 ⚫️ 🔜 🛠️ 🕐 🔠 🌀 ❌. & ⤴️ 💲 📨 🔠 👈 🌀 ❌ 🔜 ⚙️ 🔄 & 🔄 🕐❔ 🔢 🤙 ⏮️ ⚫️❔ 🎏 🌀 ❌. + +🖼, 🚥 👆 ✔️ 🔢: + +```Python +@lru_cache() +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +👆 📋 💪 🛠️ 💖 👉: + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end +``` + +💼 👆 🔗 `get_settings()`, 🔢 🚫 ✊ 🙆 ❌, ⚫️ 🕧 📨 🎏 💲. + +👈 🌌, ⚫️ 🎭 🌖 🚥 ⚫️ 🌐 🔢. ✋️ ⚫️ ⚙️ 🔗 🔢, ⤴️ 👥 💪 🔐 ⚫️ 💪 🔬. + +`@lru_cache()` 🍕 `functools` ❔ 🍕 🐍 🐩 🗃, 👆 💪 ✍ 🌅 🔃 ⚫️ 🐍 🩺 `@lru_cache()`. + +## 🌃 + +👆 💪 ⚙️ Pydantic ⚒ 🍵 ⚒ ⚖️ 📳 👆 🈸, ⏮️ 🌐 🏋️ Pydantic 🏷. + +* ⚙️ 🔗 👆 💪 📉 🔬. +* 👆 💪 ⚙️ `.env` 📁 ⏮️ ⚫️. +* ⚙️ `@lru_cache()` ➡️ 👆 ❎ 👂 🇨🇻 📁 🔄 & 🔄 🔠 📨, ⏪ 🤝 👆 🔐 ⚫️ ⏮️ 🔬. diff --git a/docs/em/docs/advanced/sql-databases-peewee.md b/docs/em/docs/advanced/sql-databases-peewee.md new file mode 100644 index 0000000000..62619fc2c3 --- /dev/null +++ b/docs/em/docs/advanced/sql-databases-peewee.md @@ -0,0 +1,529 @@ +# 🗄 (🔗) 💽 ⏮️ 🏒 + +!!! warning + 🚥 👆 ▶️, 🔰 [🗄 (🔗) 💽](../tutorial/sql-databases.md){.internal-link target=_blank} 👈 ⚙️ 🇸🇲 🔜 🥃. + + 💭 🆓 🚶 👉. + +🚥 👆 ▶️ 🏗 ⚪️➡️ 🖌, 👆 🎲 👻 📆 ⏮️ 🇸🇲 🐜 ([🗄 (🔗) 💽](../tutorial/sql-databases.md){.internal-link target=_blank}), ⚖️ 🙆 🎏 🔁 🐜. + +🚥 👆 ⏪ ✔️ 📟 🧢 👈 ⚙️ 🏒 🐜, 👆 💪 ✅ 📥 ❔ ⚙️ ⚫️ ⏮️ **FastAPI**. + +!!! warning "🐍 3️⃣.7️⃣ ➕ ✔" + 👆 🔜 💪 🐍 3️⃣.7️⃣ ⚖️ 🔛 🔒 ⚙️ 🏒 ⏮️ FastAPI. + +## 🏒 🔁 + +🏒 🚫 🔧 🔁 🛠️, ⚖️ ⏮️ 👫 🤯. + +🏒 ✔️ 🏋️ 🔑 🔃 🚮 🔢 & 🔃 ❔ ⚫️ 🔜 ⚙️. + +🚥 👆 🛠️ 🈸 ⏮️ 🗝 🚫-🔁 🛠️, & 💪 👷 ⏮️ 🌐 🚮 🔢, **⚫️ 💪 👑 🧰**. + +✋️ 🚥 👆 💪 🔀 🔢, 🐕‍🦺 🌖 🌘 1️⃣ 🔁 💽, 👷 ⏮️ 🔁 🛠️ (💖 FastAPI), ♒️, 👆 🔜 💪 🚮 🏗 ➕ 📟 🔐 👈 🔢. + +👐, ⚫️ 💪 ⚫️, & 📥 👆 🔜 👀 ⚫️❔ ⚫️❔ 📟 👆 ✔️ 🚮 💪 ⚙️ 🏒 ⏮️ FastAPI. + +!!! note "📡 ℹ" + 👆 💪 ✍ 🌅 🔃 🏒 🧍 🔃 🔁 🐍 🩺, , 🇵🇷. + +## 🎏 📱 + +👥 🔜 ✍ 🎏 🈸 🇸🇲 🔰 ([🗄 (🔗) 💽](../tutorial/sql-databases.md){.internal-link target=_blank}). + +🌅 📟 🤙 🎏. + +, 👥 🔜 🎯 🕴 🔛 🔺. + +## 📁 📊 + +➡️ 💬 👆 ✔️ 📁 📛 `my_super_project` 👈 🔌 🎧-📁 🤙 `sql_app` ⏮️ 📊 💖 👉: + +``` +. +└── sql_app + ├── __init__.py + ├── crud.py + ├── database.py + ├── main.py + └── schemas.py +``` + +👉 🌖 🎏 📊 👥 ✔️ 🇸🇲 🔰. + +🔜 ➡️ 👀 ⚫️❔ 🔠 📁/🕹 🔨. + +## ✍ 🏒 🍕 + +➡️ 🔗 📁 `sql_app/database.py`. + +### 🐩 🏒 📟 + +➡️ 🥇 ✅ 🌐 😐 🏒 📟, ✍ 🏒 💽: + +```Python hl_lines="3 5 22" +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +!!! tip + ✔️ 🤯 👈 🚥 👆 💚 ⚙️ 🎏 💽, 💖 ✳, 👆 🚫 🚫 🔀 🎻. 👆 🔜 💪 ⚙️ 🎏 🏒 💽 🎓. + +#### 🗒 + +❌: + +```Python +check_same_thread=False +``` + +🌓 1️⃣ 🇸🇲 🔰: + +```Python +connect_args={"check_same_thread": False} +``` + +...⚫️ 💪 🕴 `SQLite`. + +!!! info "📡 ℹ" + + ⚫️❔ 🎏 📡 ℹ [🗄 (🔗) 💽](../tutorial/sql-databases.md#note){.internal-link target=_blank} ✔. + +### ⚒ 🏒 🔁-🔗 `PeeweeConnectionState` + +👑 ❔ ⏮️ 🏒 & FastAPI 👈 🏒 ⚓️ 🙇 🔛 🐍 `threading.local`, & ⚫️ 🚫 ✔️ 🎯 🌌 🔐 ⚫️ ⚖️ ➡️ 👆 🍵 🔗/🎉 🔗 (🔨 🇸🇲 🔰). + +& `threading.local` 🚫 🔗 ⏮️ 🆕 🔁 ⚒ 🏛 🐍. + +!!! note "📡 ℹ" + `threading.local` ⚙️ ✔️ "🎱" 🔢 👈 ✔️ 🎏 💲 🔠 🧵. + + 👉 ⚠ 🗝 🛠️ 🏗 ✔️ 1️⃣ 👁 🧵 📍 📨, 🙅‍♂ 🌖, 🙅‍♂ 🌘. + + ⚙️ 👉, 🔠 📨 🔜 ✔️ 🚮 👍 💽 🔗/🎉, ❔ ☑ 🏁 🥅. + + ✋️ FastAPI, ⚙️ 🆕 🔁 ⚒, 💪 🍵 🌅 🌘 1️⃣ 📨 🔛 🎏 🧵. & 🎏 🕰, 👁 📨, ⚫️ 💪 🏃 💗 👜 🎏 🧵 (🧵), ⚓️ 🔛 🚥 👆 ⚙️ `async def` ⚖️ 😐 `def`. 👉 ⚫️❔ 🤝 🌐 🎭 📈 FastAPI. + +✋️ 🐍 3️⃣.7️⃣ & 🔛 🚚 🌖 🏧 🎛 `threading.local`, 👈 💪 ⚙️ 🥉 🌐❔ `threading.local` 🔜 ⚙️, ✋️ 🔗 ⏮️ 🆕 🔁 ⚒. + +👥 🔜 ⚙️ 👈. ⚫️ 🤙 `contextvars`. + +👥 🔜 🔐 🔗 🍕 🏒 👈 ⚙️ `threading.local` & ❎ 👫 ⏮️ `contextvars`, ⏮️ 🔗 ℹ. + +👉 5️⃣📆 😑 🍖 🏗 (& ⚫️ 🤙), 👆 🚫 🤙 💪 🍕 🤔 ❔ ⚫️ 👷 ⚙️ ⚫️. + +👥 🔜 ✍ `PeeweeConnectionState`: + +```Python hl_lines="10-19" +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +👉 🎓 😖 ⚪️➡️ 🎁 🔗 🎓 ⚙️ 🏒. + +⚫️ ✔️ 🌐 ⚛ ⚒ 🏒 ⚙️ `contextvars` ↩️ `threading.local`. + +`contextvars` 👷 🍖 🎏 🌘 `threading.local`. ✋️ 🎂 🏒 🔗 📟 🤔 👈 👉 🎓 👷 ⏮️ `threading.local`. + +, 👥 💪 ➕ 🎱 ⚒ ⚫️ 👷 🚥 ⚫️ ⚙️ `threading.local`. `__init__`, `__setattr__`, & `__getattr__` 🛠️ 🌐 ✔ 🎱 👉 ⚙️ 🏒 🍵 🤔 👈 ⚫️ 🔜 🔗 ⏮️ FastAPI. + +!!! tip + 👉 🔜 ⚒ 🏒 🎭 ☑ 🕐❔ ⚙️ ⏮️ FastAPI. 🚫 🎲 📂 ⚖️ 📪 🔗 👈 ➖ ⚙️, 🏗 ❌, ♒️. + + ✋️ ⚫️ 🚫 🤝 🏒 🔁 💎-🏋️. 👆 🔜 ⚙️ 😐 `def` 🔢 & 🚫 `async def`. + +### ⚙️ 🛃 `PeeweeConnectionState` 🎓 + +🔜, 📁 `._state` 🔗 🔢 🏒 💽 `db` 🎚 ⚙️ 🆕 `PeeweeConnectionState`: + +```Python hl_lines="24" +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +!!! tip + ⚒ 💭 👆 📁 `db._state` *⏮️* 🏗 `db`. + +!!! tip + 👆 🔜 🎏 🙆 🎏 🏒 💽, 🔌 `PostgresqlDatabase`, `MySQLDatabase`, ♒️. + +## ✍ 💽 🏷 + +➡️ 🔜 👀 📁 `sql_app/models.py`. + +### ✍ 🏒 🏷 👆 💽 + +🔜 ✍ 🏒 🏷 (🎓) `User` & `Item`. + +👉 🎏 👆 🔜 🚥 👆 ⏩ 🏒 🔰 & ℹ 🏷 ✔️ 🎏 💽 🇸🇲 🔰. + +!!! tip + 🏒 ⚙️ ⚖ "**🏷**" 🔗 👉 🎓 & 👐 👈 🔗 ⏮️ 💽. + + ✋️ Pydantic ⚙️ ⚖ "**🏷**" 🔗 🕳 🎏, 💽 🔬, 🛠️, & 🧾 🎓 & 👐. + +🗄 `db` ⚪️➡️ `database` (📁 `database.py` ⚪️➡️ 🔛) & ⚙️ ⚫️ 📥. + +```Python hl_lines="3 6-12 15-21" +{!../../../docs_src/sql_databases_peewee/sql_app/models.py!} +``` + +!!! tip + 🏒 ✍ 📚 🎱 🔢. + + ⚫️ 🔜 🔁 🚮 `id` 🔢 🔢 👑 🔑. + + ⚫️ 🔜 ⚒ 📛 🏓 ⚓️ 🔛 🎓 📛. + + `Item`, ⚫️ 🔜 ✍ 🔢 `owner_id` ⏮️ 🔢 🆔 `User`. ✋️ 👥 🚫 📣 ⚫️ 🙆. + +## ✍ Pydantic 🏷 + +🔜 ➡️ ✅ 📁 `sql_app/schemas.py`. + +!!! tip + ❎ 😨 🖖 🏒 *🏷* & Pydantic *🏷*, 👥 🔜 ✔️ 📁 `models.py` ⏮️ 🏒 🏷, & 📁 `schemas.py` ⏮️ Pydantic 🏷. + + 👫 Pydantic 🏷 🔬 🌅 ⚖️ 🌘 "🔗" (☑ 📊 💠). + + 👉 🔜 ℹ 👥 ❎ 😨 ⏪ ⚙️ 👯‍♂️. + +### ✍ Pydantic *🏷* / 🔗 + +✍ 🌐 🎏 Pydantic 🏷 🇸🇲 🔰: + +```Python hl_lines="16-18 21-22 25-30 34-35 38-39 42-48" +{!../../../docs_src/sql_databases_peewee/sql_app/schemas.py!} +``` + +!!! tip + 📥 👥 🏗 🏷 ⏮️ `id`. + + 👥 🚫 🎯 ✔ `id` 🔢 🏒 🏷, ✋️ 🏒 🚮 1️⃣ 🔁. + + 👥 ❎ 🎱 `owner_id` 🔢 `Item`. + +### ✍ `PeeweeGetterDict` Pydantic *🏷* / 🔗 + +🕐❔ 👆 🔐 💛 🏒 🎚, 💖 `some_user.items`, 🏒 🚫 🚚 `list` `Item`. + +⚫️ 🚚 🎁 🛃 🎚 🎓 `ModelSelect`. + +⚫️ 💪 ✍ `list` 🚮 🏬 ⏮️ `list(some_user.items)`. + +✋️ 🎚 ⚫️ 🚫 `list`. & ⚫️ 🚫 ☑ 🐍 🚂. ↩️ 👉, Pydantic 🚫 💭 🔢 ❔ 🗜 ⚫️ `list` Pydantic *🏷* / 🔗. + +✋️ ⏮️ ⏬ Pydantic ✔ 🚚 🛃 🎓 👈 😖 ⚪️➡️ `pydantic.utils.GetterDict`, 🚚 🛠️ ⚙️ 🕐❔ ⚙️ `orm_mode = True` 🗃 💲 🐜 🏷 🔢. + +👥 🔜 ✍ 🛃 `PeeweeGetterDict` 🎓 & ⚙️ ⚫️ 🌐 🎏 Pydantic *🏷* / 🔗 👈 ⚙️ `orm_mode`: + +```Python hl_lines="3 8-13 31 49" +{!../../../docs_src/sql_databases_peewee/sql_app/schemas.py!} +``` + +📥 👥 ✅ 🚥 🔢 👈 ➖ 🔐 (✅ `.items` `some_user.items`) 👐 `peewee.ModelSelect`. + +& 🚥 👈 💼, 📨 `list` ⏮️ ⚫️. + +& ⤴️ 👥 ⚙️ ⚫️ Pydantic *🏷* / 🔗 👈 ⚙️ `orm_mode = True`, ⏮️ 📳 🔢 `getter_dict = PeeweeGetterDict`. + +!!! tip + 👥 🕴 💪 ✍ 1️⃣ `PeeweeGetterDict` 🎓, & 👥 💪 ⚙️ ⚫️ 🌐 Pydantic *🏷* / 🔗. + +## 💩 🇨🇻 + +🔜 ➡️ 👀 📁 `sql_app/crud.py`. + +### ✍ 🌐 💩 🇨🇻 + +✍ 🌐 🎏 💩 🇨🇻 🇸🇲 🔰, 🌐 📟 📶 🎏: + +```Python hl_lines="1 4-5 8-9 12-13 16-20 23-24 27-30" +{!../../../docs_src/sql_databases_peewee/sql_app/crud.py!} +``` + +📤 🔺 ⏮️ 📟 🇸🇲 🔰. + +👥 🚫 🚶‍♀️ `db` 🔢 🤭. ↩️ 👥 ⚙️ 🏷 🔗. 👉 ↩️ `db` 🎚 🌐 🎚, 👈 🔌 🌐 🔗 ⚛. 👈 ⚫️❔ 👥 ✔️ 🌐 `contextvars` ℹ 🔛. + +🆖, 🕐❔ 🛬 📚 🎚, 💖 `get_users`, 👥 🔗 🤙 `list`, 💖: + +```Python +list(models.User.select()) +``` + +👉 🎏 🤔 👈 👥 ✔️ ✍ 🛃 `PeeweeGetterDict`. ✋️ 🛬 🕳 👈 ⏪ `list` ↩️ `peewee.ModelSelect` `response_model` *➡ 🛠️* ⏮️ `List[models.User]` (👈 👥 🔜 👀 ⏪) 🔜 👷 ☑. + +## 👑 **FastAPI** 📱 + +& 🔜 📁 `sql_app/main.py` ➡️ 🛠️ & ⚙️ 🌐 🎏 🍕 👥 ✍ ⏭. + +### ✍ 💽 🏓 + +📶 🙃 🌌 ✍ 💽 🏓: + +```Python hl_lines="9-11" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +### ✍ 🔗 + +✍ 🔗 👈 🔜 🔗 💽 ▶️️ ▶️ 📨 & 🔌 ⚫️ 🔚: + +```Python hl_lines="23-29" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +📥 👥 ✔️ 🛁 `yield` ↩️ 👥 🤙 🚫 ⚙️ 💽 🎚 🔗. + +⚫️ 🔗 💽 & ♻ 🔗 💽 🔗 🔢 👈 🔬 🔠 📨 (⚙️ `contextvars` 🎱 ⚪️➡️ 🔛). + +↩️ 💽 🔗 ⚠ 👤/🅾 🚧, 👉 🔗 ✍ ⏮️ 😐 `def` 🔢. + +& ⤴️, 🔠 *➡ 🛠️ 🔢* 👈 💪 🔐 💽 👥 🚮 ⚫️ 🔗. + +✋️ 👥 🚫 ⚙️ 💲 👐 👉 🔗 (⚫️ 🤙 🚫 🤝 🙆 💲, ⚫️ ✔️ 🛁 `yield`). , 👥 🚫 🚮 ⚫️ *➡ 🛠️ 🔢* ✋️ *➡ 🛠️ 👨‍🎨* `dependencies` 🔢: + +```Python hl_lines="32 40 47 59 65 72" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +### 🔑 🔢 🎧-🔗 + +🌐 `contextvars` 🍕 👷, 👥 💪 ⚒ 💭 👥 ✔️ 🔬 💲 `ContextVar` 🔠 📨 👈 ⚙️ 💽, & 👈 💲 🔜 ⚙️ 💽 🇵🇸 (🔗, 💵, ♒️) 🎂 📨. + +👈, 👥 💪 ✍ ➕1️⃣ `async` 🔗 `reset_db_state()` 👈 ⚙️ 🎧-🔗 `get_db()`. ⚫️ 🔜 ⚒ 💲 🔑 🔢 (⏮️ 🔢 `dict`) 👈 🔜 ⚙️ 💽 🇵🇸 🎂 📨. & ⤴️ 🔗 `get_db()` 🔜 🏪 ⚫️ 💽 🇵🇸 (🔗, 💵, ♒️). + +```Python hl_lines="18-20" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +**⏭ 📨**, 👥 🔜 ⏲ 👈 🔑 🔢 🔄 `async` 🔗 `reset_db_state()` & ⤴️ ✍ 🆕 🔗 `get_db()` 🔗, 👈 🆕 📨 🔜 ✔️ 🚮 👍 💽 🇵🇸 (🔗, 💵, ♒️). + +!!! tip + FastAPI 🔁 🛠️, 1️⃣ 📨 💪 ▶️ ➖ 🛠️, & ⏭ 🏁, ➕1️⃣ 📨 💪 📨 & ▶️ 🏭 👍, & ⚫️ 🌐 💪 🛠️ 🎏 🧵. + + ✋️ 🔑 🔢 🤔 👫 🔁 ⚒,, 🏒 💽 🇵🇸 ⚒ `async` 🔗 `reset_db_state()` 🔜 🚧 🚮 👍 💽 🎂 🎂 📨. + + & 🎏 🕰, 🎏 🛠️ 📨 🔜 ✔️ 🚮 👍 💽 🇵🇸 👈 🔜 🔬 🎂 📨. + +#### 🏒 🗳 + +🚥 👆 ⚙️ 🏒 🗳, ☑ 💽 `db.obj`. + +, 👆 🔜 ⏲ ⚫️ ⏮️: + +```Python hl_lines="3-4" +async def reset_db_state(): + database.db.obj._state._state.set(db_state_default.copy()) + database.db.obj._state.reset() +``` + +### ✍ 👆 **FastAPI** *➡ 🛠️* + +🔜, 😒, 📥 🐩 **FastAPI** *➡ 🛠️* 📟. + +```Python hl_lines="32-37 40-43 46-53 56-62 65-68 71-79" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +### 🔃 `def` 🆚 `async def` + +🎏 ⏮️ 🇸🇲, 👥 🚫 🔨 🕳 💖: + +```Python +user = await models.User.select().first() +``` + +...✋️ ↩️ 👥 ⚙️: + +```Python +user = models.User.select().first() +``` + +, 🔄, 👥 🔜 📣 *➡ 🛠️ 🔢* & 🔗 🍵 `async def`, ⏮️ 😐 `def`,: + +```Python hl_lines="2" +# Something goes here +def read_users(skip: int = 0, limit: int = 100): + # Something goes here +``` + +## 🔬 🏒 ⏮️ 🔁 + +👉 🖼 🔌 ➕ *➡ 🛠️* 👈 🔬 📏 🏭 📨 ⏮️ `time.sleep(sleep_time)`. + +⚫️ 🔜 ✔️ 💽 🔗 📂 ▶️ & 🔜 ⌛ 🥈 ⏭ 🙇 🔙. & 🔠 🆕 📨 🔜 ⌛ 🕐 🥈 🌘. + +👉 🔜 💪 ➡️ 👆 💯 👈 👆 📱 ⏮️ 🏒 & FastAPI 🎭 ☑ ⏮️ 🌐 💩 🔃 🧵. + +🚥 👆 💚 ✅ ❔ 🏒 🔜 💔 👆 📱 🚥 ⚙️ 🍵 🛠️, 🚶 `sql_app/database.py` 📁 & 🏤 ⏸: + +```Python +# db._state = PeeweeConnectionState() +``` + +& 📁 `sql_app/main.py` 📁, 🏤 💪 `async` 🔗 `reset_db_state()` & ❎ ⚫️ ⏮️ `pass`: + +```Python +async def reset_db_state(): +# database.db._state._state.set(db_state_default.copy()) +# database.db._state.reset() + pass +``` + +⤴️ 🏃 👆 📱 ⏮️ Uvicorn: + +
+ +```console +$ uvicorn sql_app.main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +📂 👆 🖥 http://127.0.0.1:8000/docs & ✍ 👩‍❤‍👨 👩‍💻. + +⤴️ 📂 1️⃣0️⃣ 📑 http://127.0.0.1:8000/docs#/default/read_🐌_👩‍💻_slowusers_ = 🎏 🕰. + +🚶 *➡ 🛠️* "🤚 `/slowusers/`" 🌐 📑. ⚙️ "🔄 ⚫️ 👅" 🔼 & 🛠️ 📨 🔠 📑, 1️⃣ ▶️️ ⏮️ 🎏. + +📑 🔜 ⌛ 🍖 & ⤴️ 👫 🔜 🎦 `Internal Server Error`. + +### ⚫️❔ 🔨 + +🥇 📑 🔜 ⚒ 👆 📱 ✍ 🔗 💽 & ⌛ 🥈 ⏭ 🙇 🔙 & 📪 💽 🔗. + +⤴️, 📨 ⏭ 📑, 👆 📱 🔜 ⌛ 🕐 🥈 🌘, & 🔛. + +👉 ⛓ 👈 ⚫️ 🔜 🔚 🆙 🏁 🏁 📑' 📨 ⏪ 🌘 ⏮️ 🕐. + +⤴️ 1️⃣ 🏁 📨 👈 ⌛ 🌘 🥈 🔜 🔄 📂 💽 🔗, ✋️ 1️⃣ 📚 ⏮️ 📨 🎏 📑 🔜 🎲 🍵 🎏 🧵 🥇 🕐, ⚫️ 🔜 ✔️ 🎏 💽 🔗 👈 ⏪ 📂, & 🏒 🔜 🚮 ❌ & 👆 🔜 👀 ⚫️ 📶, & 📨 🔜 ✔️ `Internal Server Error`. + +👉 🔜 🎲 🔨 🌅 🌘 1️⃣ 📚 📑. + +🚥 👆 ✔️ 💗 👩‍💻 💬 👆 📱 ⚫️❔ 🎏 🕰, 👉 ⚫️❔ 💪 🔨. + +& 👆 📱 ▶️ 🍵 🌅 & 🌖 👩‍💻 🎏 🕰, ⌛ 🕰 👁 📨 💪 📏 & 📏 ⏲ ❌. + +### 🔧 🏒 ⏮️ FastAPI + +🔜 🚶 🔙 📁 `sql_app/database.py`, & ✍ ⏸: + +```Python +db._state = PeeweeConnectionState() +``` + +& 📁 `sql_app/main.py` 📁, ✍ 💪 `async` 🔗 `reset_db_state()`: + +```Python +async def reset_db_state(): + database.db._state._state.set(db_state_default.copy()) + database.db._state.reset() +``` + +❎ 👆 🏃‍♂ 📱 & ▶️ ⚫️ 🔄. + +🔁 🎏 🛠️ ⏮️ 1️⃣0️⃣ 📑. 👉 🕰 🌐 👫 🔜 ⌛ & 👆 🔜 🤚 🌐 🏁 🍵 ❌. + +...👆 🔧 ⚫️ ❗ + +## 📄 🌐 📁 + + 💭 👆 🔜 ✔️ 📁 📛 `my_super_project` (⚖️ 👐 👆 💚) 👈 🔌 🎧-📁 🤙 `sql_app`. + +`sql_app` 🔜 ✔️ 📄 📁: + +* `sql_app/__init__.py`: 🛁 📁. + +* `sql_app/database.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +* `sql_app/models.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/models.py!} +``` + +* `sql_app/schemas.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/schemas.py!} +``` + +* `sql_app/crud.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/crud.py!} +``` + +* `sql_app/main.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +## 📡 ℹ + +!!! warning + 👉 📶 📡 ℹ 👈 👆 🎲 🚫 💪. + +### ⚠ + +🏒 ⚙️ `threading.local` 🔢 🏪 ⚫️ 💽 "🇵🇸" 💽 (🔗, 💵, ♒️). + +`threading.local` ✍ 💲 🌟 ⏮️ 🧵, ✋️ 🔁 🛠️ 🔜 🏃 🌐 📟 (✅ 🔠 📨) 🎏 🧵, & 🎲 🚫 ✔. + +🔛 🔝 👈, 🔁 🛠️ 💪 🏃 🔁 📟 🧵 (⚙️ `asyncio.run_in_executor`), ✋️ 🔗 🎏 📨. + +👉 ⛓ 👈, ⏮️ 🏒 ⏮️ 🛠️, 💗 📋 💪 ⚙️ 🎏 `threading.local` 🔢 & 🔚 🆙 🤝 🎏 🔗 & 💽 (👈 👫 🚫🔜 🚫), & 🎏 🕰, 🚥 👫 🛠️ 🔁 👤/🅾-🚧 📟 🧵 (⏮️ 😐 `def` 🔢 FastAPI, *➡ 🛠️* & 🔗), 👈 📟 🏆 🚫 ✔️ 🔐 💽 🇵🇸 🔢, ⏪ ⚫️ 🍕 🎏 📨 & ⚫️ 🔜 💪 🤚 🔐 🎏 💽 🇵🇸. + +### 🔑 🔢 + +🐍 3️⃣.7️⃣ ✔️ `contextvars` 👈 💪 ✍ 🇧🇿 🔢 📶 🎏 `threading.local`, ✋️ 🔗 👫 🔁 ⚒. + +📤 📚 👜 ✔️ 🤯. + +`ContextVar` ✔️ ✍ 🔝 🕹, 💖: + +```Python +some_var = ContextVar("some_var", default="default value") +``` + +⚒ 💲 ⚙️ ⏮️ "🔑" (✅ ⏮️ 📨) ⚙️: + +```Python +some_var.set("new value") +``` + +🤚 💲 🙆 🔘 🔑 (✅ 🙆 🍕 🚚 ⏮️ 📨) ⚙️: + +```Python +some_var.get() +``` + +### ⚒ 🔑 🔢 `async` 🔗 `reset_db_state()` + +🚥 🍕 🔁 📟 ⚒ 💲 ⏮️ `some_var.set("updated in function")` (✅ 💖 `async` 🔗), 🎂 📟 ⚫️ & 📟 👈 🚶 ⏮️ (✅ 📟 🔘 `async` 🔢 🤙 ⏮️ `await`) 🔜 👀 👈 🆕 💲. + +, 👆 💼, 🚥 👥 ⚒ 🏒 🇵🇸 🔢 (⏮️ 🔢 `dict`) `async` 🔗, 🌐 🎂 🔗 📟 👆 📱 🔜 👀 👉 💲 & 🔜 💪 ♻ ⚫️ 🎂 📨. + +& 🔑 🔢 🔜 ⚒ 🔄 ⏭ 📨, 🚥 👫 🛠️. + +### ⚒ 💽 🇵🇸 🔗 `get_db()` + +`get_db()` 😐 `def` 🔢, **FastAPI** 🔜 ⚒ ⚫️ 🏃 🧵, ⏮️ *📁* "🔑", 🧑‍🤝‍🧑 🎏 💲 🔑 🔢 ( `dict` ⏮️ ⏲ 💽 🇵🇸). ⤴️ ⚫️ 💪 🚮 💽 🇵🇸 👈 `dict`, 💖 🔗, ♒️. + +✋️ 🚥 💲 🔑 🔢 (🔢 `dict`) ⚒ 👈 😐 `def` 🔢, ⚫️ 🔜 ✍ 🆕 💲 👈 🔜 🚧 🕴 👈 🧵 🧵, & 🎂 📟 (💖 *➡ 🛠️ 🔢*) 🚫🔜 ✔️ 🔐 ⚫️. `get_db()` 👥 💪 🕴 ⚒ 💲 `dict`, ✋️ 🚫 🎂 `dict` ⚫️. + +, 👥 💪 ✔️ `async` 🔗 `reset_db_state()` ⚒ `dict` 🔑 🔢. 👈 🌌, 🌐 📟 ✔️ 🔐 🎏 `dict` 💽 🇵🇸 👁 📨. + +### 🔗 & 🔌 🔗 `get_db()` + +⤴️ ⏭ ❔ 🔜, ⚫️❔ 🚫 🔗 & 🔌 💽 `async` 🔗 ⚫️, ↩️ `get_db()`❓ + +`async` 🔗 ✔️ `async` 🔑 🔢 🛡 🎂 📨, ✋️ 🏗 & 📪 💽 🔗 ⚠ 🚧, ⚫️ 💪 📉 🎭 🚥 ⚫️ 📤. + +👥 💪 😐 `def` 🔗 `get_db()`. diff --git a/docs/em/docs/advanced/sub-applications.md b/docs/em/docs/advanced/sub-applications.md new file mode 100644 index 0000000000..e0391453be --- /dev/null +++ b/docs/em/docs/advanced/sub-applications.md @@ -0,0 +1,73 @@ +# 🎧 🈸 - 🗻 + +🚥 👆 💪 ✔️ 2️⃣ 🔬 FastAPI 🈸, ⏮️ 👫 👍 🔬 🗄 & 👫 👍 🩺 ⚜, 👆 💪 ✔️ 👑 📱 & "🗻" 1️⃣ (⚖️ 🌅) 🎧-🈸(Ⓜ). + +## 🗜 **FastAPI** 🈸 + +"🗜" ⛓ ❎ 🍕 "🔬" 🈸 🎯 ➡, 👈 ⤴️ ✊ 💅 🚚 🌐 🔽 👈 ➡, ⏮️ _➡ 🛠️_ 📣 👈 🎧-🈸. + +### 🔝-🎚 🈸 + +🥇, ✍ 👑, 🔝-🎚, **FastAPI** 🈸, & 🚮 *➡ 🛠️*: + +```Python hl_lines="3 6-8" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### 🎧-🈸 + +⤴️, ✍ 👆 🎧-🈸, & 🚮 *➡ 🛠️*. + +👉 🎧-🈸 ➕1️⃣ 🐩 FastAPI 🈸, ✋️ 👉 1️⃣ 👈 🔜 "🗻": + +```Python hl_lines="11 14-16" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### 🗻 🎧-🈸 + +👆 🔝-🎚 🈸, `app`, 🗻 🎧-🈸, `subapi`. + +👉 💼, ⚫️ 🔜 📌 ➡ `/subapi`: + +```Python hl_lines="11 19" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### ✅ 🏧 🛠️ 🩺 + +🔜, 🏃 `uvicorn` ⏮️ 👑 📱, 🚥 👆 📁 `main.py`, ⚫️ 🔜: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +& 📂 🩺 http://127.0.0.1:8000/docs. + +👆 🔜 👀 🏧 🛠️ 🩺 👑 📱, 🔌 🕴 🚮 👍 _➡ 🛠️_: + + + +& ⤴️, 📂 🩺 🎧-🈸, http://127.0.0.1:8000/subapi/docs. + +👆 🔜 👀 🏧 🛠️ 🩺 🎧-🈸, ✅ 🕴 🚮 👍 _➡ 🛠️_, 🌐 🔽 ☑ 🎧-➡ 🔡 `/subapi`: + + + +🚥 👆 🔄 🔗 ⏮️ 🙆 2️⃣ 👩‍💻 🔢, 👫 🔜 👷 ☑, ↩️ 🖥 🔜 💪 💬 🔠 🎯 📱 ⚖️ 🎧-📱. + +### 📡 ℹ: `root_path` + +🕐❔ 👆 🗻 🎧-🈸 🔬 🔛, FastAPI 🔜 ✊ 💅 🔗 🗻 ➡ 🎧-🈸 ⚙️ 🛠️ ⚪️➡️ 🔫 🔧 🤙 `root_path`. + +👈 🌌, 🎧-🈸 🔜 💭 ⚙️ 👈 ➡ 🔡 🩺 🎚. + +& 🎧-🈸 💪 ✔️ 🚮 👍 📌 🎧-🈸 & 🌐 🔜 👷 ☑, ↩️ FastAPI 🍵 🌐 👉 `root_path`Ⓜ 🔁. + +👆 🔜 💡 🌅 🔃 `root_path` & ❔ ⚙️ ⚫️ 🎯 📄 🔃 [⛅ 🗳](./behind-a-proxy.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/templates.md b/docs/em/docs/advanced/templates.md new file mode 100644 index 0000000000..1fb57725af --- /dev/null +++ b/docs/em/docs/advanced/templates.md @@ -0,0 +1,77 @@ +# 📄 + +👆 💪 ⚙️ 🙆 📄 🚒 👆 💚 ⏮️ **FastAPI**. + +⚠ ⚒ Jinja2️⃣, 🎏 1️⃣ ⚙️ 🏺 & 🎏 🧰. + +📤 🚙 🔗 ⚫️ 💪 👈 👆 💪 ⚙️ 🔗 👆 **FastAPI** 🈸 (🚚 💃). + +## ❎ 🔗 + +❎ `jinja2`: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## ⚙️ `Jinja2Templates` + +* 🗄 `Jinja2Templates`. +* ✍ `templates` 🎚 👈 👆 💪 🏤-⚙️ ⏪. +* 📣 `Request` 🔢 *➡ 🛠️* 👈 🔜 📨 📄. +* ⚙️ `templates` 👆 ✍ ✍ & 📨 `TemplateResponse`, 🚶‍♀️ `request` 1️⃣ 🔑-💲 👫 Jinja2️⃣ "🔑". + +```Python hl_lines="4 11 15-16" +{!../../../docs_src/templates/tutorial001.py!} +``` + +!!! note + 👀 👈 👆 ✔️ 🚶‍♀️ `request` 🍕 🔑-💲 👫 🔑 Jinja2️⃣. , 👆 ✔️ 📣 ⚫️ 👆 *➡ 🛠️*. + +!!! tip + 📣 `response_class=HTMLResponse` 🩺 🎚 🔜 💪 💭 👈 📨 🔜 🕸. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.templating import Jinja2Templates`. + + **FastAPI** 🚚 🎏 `starlette.templating` `fastapi.templating` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `Request` & `StaticFiles`. + +## ✍ 📄 + +⤴️ 👆 💪 ✍ 📄 `templates/item.html` ⏮️: + +```jinja hl_lines="7" +{!../../../docs_src/templates/templates/item.html!} +``` + +⚫️ 🔜 🎦 `id` ✊ ⚪️➡️ "🔑" `dict` 👆 🚶‍♀️: + +```Python +{"request": request, "id": id} +``` + +## 📄 & 🎻 📁 + +& 👆 💪 ⚙️ `url_for()` 🔘 📄, & ⚙️ ⚫️, 🖼, ⏮️ `StaticFiles` 👆 📌. + +```jinja hl_lines="4" +{!../../../docs_src/templates/templates/item.html!} +``` + +👉 🖼, ⚫️ 🔜 🔗 🎚 📁 `static/styles.css` ⏮️: + +```CSS hl_lines="4" +{!../../../docs_src/templates/static/styles.css!} +``` + +& ↩️ 👆 ⚙️ `StaticFiles`, 👈 🎚 📁 🔜 🍦 🔁 👆 **FastAPI** 🈸 📛 `/static/styles.css`. + +## 🌅 ℹ + +🌅 ℹ, 🔌 ❔ 💯 📄, ✅ 💃 🩺 🔛 📄. diff --git a/docs/em/docs/advanced/testing-database.md b/docs/em/docs/advanced/testing-database.md new file mode 100644 index 0000000000..93acd710e8 --- /dev/null +++ b/docs/em/docs/advanced/testing-database.md @@ -0,0 +1,95 @@ +# 🔬 💽 + +👆 💪 ⚙️ 🎏 🔗 🔐 ⚪️➡️ [🔬 🔗 ⏮️ 🔐](testing-dependencies.md){.internal-link target=_blank} 📉 💽 🔬. + +👆 💪 💚 ⚒ 🆙 🎏 💽 🔬, 💾 💽 ⏮️ 💯, 🏤-🥧 ⚫️ ⏮️ 🔬 💽, ♒️. + +👑 💭 ⚫️❔ 🎏 👆 👀 👈 ⏮️ 📃. + +## 🚮 💯 🗄 📱 + +➡️ ℹ 🖼 ⚪️➡️ [🗄 (🔗) 💽](../tutorial/sql-databases.md){.internal-link target=_blank} ⚙️ 🔬 💽. + +🌐 📱 📟 🎏, 👆 💪 🚶 🔙 👈 📃 ✅ ❔ ⚫️. + +🕴 🔀 📥 🆕 🔬 📁. + +👆 😐 🔗 `get_db()` 🔜 📨 💽 🎉. + +💯, 👆 💪 ⚙️ 🔗 🔐 📨 👆 *🛃* 💽 🎉 ↩️ 1️⃣ 👈 🔜 ⚙️ 🛎. + +👉 🖼 👥 🔜 ✍ 🍕 💽 🕴 💯. + +## 📁 📊 + +👥 ✍ 🆕 📁 `sql_app/tests/test_sql_app.py`. + +🆕 📁 📊 👀 💖: + +``` hl_lines="9-11" +. +└── sql_app + ├── __init__.py + ├── crud.py + ├── database.py + ├── main.py + ├── models.py + ├── schemas.py + └── tests + ├── __init__.py + └── test_sql_app.py +``` + +## ✍ 🆕 💽 🎉 + +🥇, 👥 ✍ 🆕 💽 🎉 ⏮️ 🆕 💽. + +💯 👥 🔜 ⚙️ 📁 `test.db` ↩️ `sql_app.db`. + +✋️ 🎂 🎉 📟 🌅 ⚖️ 🌘 🎏, 👥 📁 ⚫️. + +```Python hl_lines="8-13" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +!!! tip + 👆 💪 📉 ❎ 👈 📟 🚮 ⚫️ 🔢 & ⚙️ ⚫️ ⚪️➡️ 👯‍♂️ `database.py` & `tests/test_sql_app.py`. + + 🦁 & 🎯 🔛 🎯 🔬 📟, 👥 🖨 ⚫️. + +## ✍ 💽 + +↩️ 🔜 👥 🔜 ⚙️ 🆕 💽 🆕 📁, 👥 💪 ⚒ 💭 👥 ✍ 💽 ⏮️: + +```Python +Base.metadata.create_all(bind=engine) +``` + +👈 🛎 🤙 `main.py`, ✋️ ⏸ `main.py` ⚙️ 💽 📁 `sql_app.db`, & 👥 💪 ⚒ 💭 👥 ✍ `test.db` 💯. + +👥 🚮 👈 ⏸ 📥, ⏮️ 🆕 📁. + +```Python hl_lines="16" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +## 🔗 🔐 + +🔜 👥 ✍ 🔗 🔐 & 🚮 ⚫️ 🔐 👆 📱. + +```Python hl_lines="19-24 27" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +!!! tip + 📟 `override_get_db()` 🌖 ⚫️❔ 🎏 `get_db()`, ✋️ `override_get_db()` 👥 ⚙️ `TestingSessionLocal` 🔬 💽 ↩️. + +## 💯 📱 + +⤴️ 👥 💪 💯 📱 🛎. + +```Python hl_lines="32-47" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +& 🌐 🛠️ 👥 ⚒ 💽 ⏮️ 💯 🔜 `test.db` 💽 ↩️ 👑 `sql_app.db`. diff --git a/docs/em/docs/advanced/testing-dependencies.md b/docs/em/docs/advanced/testing-dependencies.md new file mode 100644 index 0000000000..104a6325ed --- /dev/null +++ b/docs/em/docs/advanced/testing-dependencies.md @@ -0,0 +1,49 @@ +# 🔬 🔗 ⏮️ 🔐 + +## 🔑 🔗 ⏮️ 🔬 + +📤 😐 🌐❔ 👆 💪 💚 🔐 🔗 ⏮️ 🔬. + +👆 🚫 💚 ⏮️ 🔗 🏃 (🚫 🙆 🎧-🔗 ⚫️ 💪 ✔️). + +↩️, 👆 💚 🚚 🎏 🔗 👈 🔜 ⚙️ 🕴 ⏮️ 💯 (🎲 🕴 🎯 💯), & 🔜 🚚 💲 👈 💪 ⚙️ 🌐❔ 💲 ⏮️ 🔗 ⚙️. + +### ⚙️ 💼: 🔢 🐕‍🦺 + +🖼 💪 👈 👆 ✔️ 🔢 🤝 🐕‍🦺 👈 👆 💪 🤙. + +👆 📨 ⚫️ 🤝 & ⚫️ 📨 🔓 👩‍💻. + +👉 🐕‍🦺 5️⃣📆 🔌 👆 📍 📨, & 🤙 ⚫️ 💪 ✊ ➕ 🕰 🌘 🚥 👆 ✔️ 🔧 🎁 👩‍💻 💯. + +👆 🎲 💚 💯 🔢 🐕‍🦺 🕐, ✋️ 🚫 🎯 🤙 ⚫️ 🔠 💯 👈 🏃. + +👉 💼, 👆 💪 🔐 🔗 👈 🤙 👈 🐕‍🦺, & ⚙️ 🛃 🔗 👈 📨 🎁 👩‍💻, 🕴 👆 💯. + +### ⚙️ `app.dependency_overrides` 🔢 + +👫 💼, 👆 **FastAPI** 🈸 ✔️ 🔢 `app.dependency_overrides`, ⚫️ 🙅 `dict`. + +🔐 🔗 🔬, 👆 🚮 🔑 ⏮️ 🔗 (🔢), & 💲, 👆 🔗 🔐 (➕1️⃣ 🔢). + +& ⤴️ **FastAPI** 🔜 🤙 👈 🔐 ↩️ ⏮️ 🔗. + +```Python hl_lines="28-29 32" +{!../../../docs_src/dependency_testing/tutorial001.py!} +``` + +!!! tip + 👆 💪 ⚒ 🔗 🔐 🔗 ⚙️ 🙆 👆 **FastAPI** 🈸. + + ⏮️ 🔗 💪 ⚙️ *➡ 🛠️ 🔢*, *➡ 🛠️ 👨‍🎨* (🕐❔ 👆 🚫 ⚙️ 📨 💲), `.include_router()` 🤙, ♒️. + + FastAPI 🔜 💪 🔐 ⚫️. + +⤴️ 👆 💪 ⏲ 👆 🔐 (❎ 👫) ⚒ `app.dependency_overrides` 🛁 `dict`: + +```Python +app.dependency_overrides = {} +``` + +!!! tip + 🚥 👆 💚 🔐 🔗 🕴 ⏮️ 💯, 👆 💪 ⚒ 🔐 ▶️ 💯 (🔘 💯 🔢) & ⏲ ⚫️ 🔚 (🔚 💯 🔢). diff --git a/docs/em/docs/advanced/testing-events.md b/docs/em/docs/advanced/testing-events.md new file mode 100644 index 0000000000..d64436eb9c --- /dev/null +++ b/docs/em/docs/advanced/testing-events.md @@ -0,0 +1,7 @@ +# 🔬 🎉: 🕴 - 🤫 + +🕐❔ 👆 💪 👆 🎉 🐕‍🦺 (`startup` & `shutdown`) 🏃 👆 💯, 👆 💪 ⚙️ `TestClient` ⏮️ `with` 📄: + +```Python hl_lines="9-12 20-24" +{!../../../docs_src/app_testing/tutorial003.py!} +``` diff --git a/docs/em/docs/advanced/testing-websockets.md b/docs/em/docs/advanced/testing-websockets.md new file mode 100644 index 0000000000..3b8e7e4208 --- /dev/null +++ b/docs/em/docs/advanced/testing-websockets.md @@ -0,0 +1,12 @@ +# 🔬 *️⃣ + +👆 💪 ⚙️ 🎏 `TestClient` 💯*️⃣. + +👉, 👆 ⚙️ `TestClient` `with` 📄, 🔗*️⃣: + +```Python hl_lines="27-31" +{!../../../docs_src/app_testing/tutorial002.py!} +``` + +!!! note + 🌅 ℹ, ✅ 💃 🧾 🔬 *️⃣ . diff --git a/docs/em/docs/advanced/using-request-directly.md b/docs/em/docs/advanced/using-request-directly.md new file mode 100644 index 0000000000..faeadb1aa2 --- /dev/null +++ b/docs/em/docs/advanced/using-request-directly.md @@ -0,0 +1,52 @@ +# ⚙️ 📨 🔗 + +🆙 🔜, 👆 ✔️ 📣 🍕 📨 👈 👆 💪 ⏮️ 👫 🆎. + +✊ 📊 ⚪️➡️: + +* ➡ 🔢. +* 🎚. +* 🍪. +* ♒️. + +& 🔨, **FastAPI** ⚖ 👈 💽, 🏭 ⚫️ & 🏭 🧾 👆 🛠️ 🔁. + +✋️ 📤 ⚠ 🌐❔ 👆 💪 💪 🔐 `Request` 🎚 🔗. + +## ℹ 🔃 `Request` 🎚 + +**FastAPI** 🤙 **💃** 🔘, ⏮️ 🧽 📚 🧰 🔛 🔝, 👆 💪 ⚙️ 💃 `Request` 🎚 🔗 🕐❔ 👆 💪. + +⚫️ 🔜 ⛓ 👈 🚥 👆 🤚 📊 ⚪️➡️ `Request` 🎚 🔗 (🖼, ✍ 💪) ⚫️ 🏆 🚫 ✔, 🗜 ⚖️ 📄 (⏮️ 🗄, 🏧 🛠️ 👩‍💻 🔢) FastAPI. + +👐 🙆 🎏 🔢 📣 🛎 (🖼, 💪 ⏮️ Pydantic 🏷) 🔜 ✔, 🗜, ✍, ♒️. + +✋️ 📤 🎯 💼 🌐❔ ⚫️ ⚠ 🤚 `Request` 🎚. + +## ⚙️ `Request` 🎚 🔗 + +➡️ 🌈 👆 💚 🤚 👩‍💻 📢 📢/🦠 🔘 👆 *➡ 🛠️ 🔢*. + +👈 👆 💪 🔐 📨 🔗. + +```Python hl_lines="1 7-8" +{!../../../docs_src/using_request_directly/tutorial001.py!} +``` + +📣 *➡ 🛠️ 🔢* 🔢 ⏮️ 🆎 ➖ `Request` **FastAPI** 🔜 💭 🚶‍♀️ `Request` 👈 🔢. + +!!! tip + 🗒 👈 👉 💼, 👥 📣 ➡ 🔢 ⤴️ 📨 🔢. + + , ➡ 🔢 🔜 ⚗, ✔, 🗜 ✔ 🆎 & ✍ ⏮️ 🗄. + + 🎏 🌌, 👆 💪 📣 🙆 🎏 🔢 🛎, & ➡, 🤚 `Request` 💁‍♂️. + +## `Request` 🧾 + +👆 💪 ✍ 🌅 ℹ 🔃 `Request` 🎚 🛂 💃 🧾 🕸. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.requests import Request`. + + **FastAPI** 🚚 ⚫️ 🔗 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. diff --git a/docs/em/docs/advanced/websockets.md b/docs/em/docs/advanced/websockets.md new file mode 100644 index 0000000000..6ba9b999dd --- /dev/null +++ b/docs/em/docs/advanced/websockets.md @@ -0,0 +1,184 @@ +# *️⃣ + +👆 💪 ⚙️ *️⃣ ⏮️ **FastAPI**. + +## ❎ `WebSockets` + +🥇 👆 💪 ❎ `WebSockets`: + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## *️⃣ 👩‍💻 + +### 🏭 + +👆 🏭 ⚙️, 👆 🎲 ✔️ 🕸 ✍ ⏮️ 🏛 🛠️ 💖 😥, Vue.js ⚖️ 📐. + +& 🔗 ⚙️ *️⃣ ⏮️ 👆 👩‍💻 👆 🔜 🎲 ⚙️ 👆 🕸 🚙. + +⚖️ 👆 💪 ✔️ 🇦🇸 📱 🈸 👈 🔗 ⏮️ 👆 *️⃣ 👩‍💻 🔗, 🇦🇸 📟. + +⚖️ 👆 5️⃣📆 ✔️ 🙆 🎏 🌌 🔗 ⏮️ *️⃣ 🔗. + +--- + +✋️ 👉 🖼, 👥 🔜 ⚙️ 📶 🙅 🕸 📄 ⏮️ 🕸, 🌐 🔘 📏 🎻. + +👉, ↗️, 🚫 ⚖ & 👆 🚫🔜 ⚙️ ⚫️ 🏭. + +🏭 👆 🔜 ✔️ 1️⃣ 🎛 🔛. + +✋️ ⚫️ 🙅 🌌 🎯 🔛 💽-🚄 *️⃣ & ✔️ 👷 🖼: + +```Python hl_lines="2 6-38 41-43" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +## ✍ `websocket` + +👆 **FastAPI** 🈸, ✍ `websocket`: + +```Python hl_lines="1 46-47" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.websockets import WebSocket`. + + **FastAPI** 🚚 🎏 `WebSocket` 🔗 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. + +## ⌛ 📧 & 📨 📧 + +👆 *️⃣ 🛣 👆 💪 `await` 📧 & 📨 📧. + +```Python hl_lines="48-52" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +👆 💪 📨 & 📨 💱, ✍, & 🎻 💽. + +## 🔄 ⚫️ + +🚥 👆 📁 📛 `main.py`, 🏃 👆 🈸 ⏮️: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +📂 👆 🖥 http://127.0.0.1:8000. + +👆 🔜 👀 🙅 📃 💖: + + + +👆 💪 🆎 📧 🔢 📦, & 📨 👫: + + + +& 👆 **FastAPI** 🈸 ⏮️ *️⃣ 🔜 📨 🔙: + + + +👆 💪 📨 (& 📨) 📚 📧: + + + +& 🌐 👫 🔜 ⚙️ 🎏 *️⃣ 🔗. + +## ⚙️ `Depends` & 🎏 + +*️⃣ 🔗 👆 💪 🗄 ⚪️➡️ `fastapi` & ⚙️: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +👫 👷 🎏 🌌 🎏 FastAPI 🔗/*➡ 🛠️*: + +```Python hl_lines="66-77 76-91" +{!../../../docs_src/websockets/tutorial002.py!} +``` + +!!! info + 👉 *️⃣ ⚫️ 🚫 🤙 ⚒ 🔑 🤚 `HTTPException`, ↩️ 👥 🤚 `WebSocketException`. + + 👆 💪 ⚙️ 📪 📟 ⚪️➡️ ☑ 📟 🔬 🔧. + +### 🔄 *️⃣ ⏮️ 🔗 + +🚥 👆 📁 📛 `main.py`, 🏃 👆 🈸 ⏮️: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +📂 👆 🖥 http://127.0.0.1:8000. + +📤 👆 💪 ⚒: + +* "🏬 🆔", ⚙️ ➡. +* "🤝" ⚙️ 🔢 🔢. + +!!! tip + 👀 👈 🔢 `token` 🔜 🍵 🔗. + +⏮️ 👈 👆 💪 🔗 *️⃣ & ⤴️ 📨 & 📨 📧: + + + +## 🚚 🔀 & 💗 👩‍💻 + +🕐❔ *️⃣ 🔗 📪, `await websocket.receive_text()` 🔜 🤚 `WebSocketDisconnect` ⚠, ❔ 👆 💪 ⤴️ ✊ & 🍵 💖 👉 🖼. + +```Python hl_lines="81-83" +{!../../../docs_src/websockets/tutorial003.py!} +``` + +🔄 ⚫️ 👅: + +* 📂 📱 ⏮️ 📚 🖥 📑. +* ✍ 📧 ⚪️➡️ 👫. +* ⤴️ 🔐 1️⃣ 📑. + +👈 🔜 🤚 `WebSocketDisconnect` ⚠, & 🌐 🎏 👩‍💻 🔜 📨 📧 💖: + +``` +Client #1596980209979 left the chat +``` + +!!! tip + 📱 🔛 ⭐ & 🙅 🖼 🎦 ❔ 🍵 & 📻 📧 📚 *️⃣ 🔗. + + ✋️ ✔️ 🤯 👈, 🌐 🍵 💾, 👁 📇, ⚫️ 🔜 🕴 👷 ⏪ 🛠️ 🏃, & 🔜 🕴 👷 ⏮️ 👁 🛠️. + + 🚥 👆 💪 🕳 ⏩ 🛠️ ⏮️ FastAPI ✋️ 👈 🌖 🏋️, 🐕‍🦺 ✳, ✳ ⚖️ 🎏, ✅ 🗜/📻. + +## 🌅 ℹ + +💡 🌅 🔃 🎛, ✅ 💃 🧾: + +* `WebSocket` 🎓. +* 🎓-⚓️ *️⃣ 🚚. diff --git a/docs/em/docs/advanced/wsgi.md b/docs/em/docs/advanced/wsgi.md new file mode 100644 index 0000000000..4d051807fb --- /dev/null +++ b/docs/em/docs/advanced/wsgi.md @@ -0,0 +1,37 @@ +# ✅ 🇨🇻 - 🏺, ✳, 🎏 + +👆 💪 🗻 🇨🇻 🈸 👆 👀 ⏮️ [🎧 🈸 - 🗻](./sub-applications.md){.internal-link target=_blank}, [⛅ 🗳](./behind-a-proxy.md){.internal-link target=_blank}. + +👈, 👆 💪 ⚙️ `WSGIMiddleware` & ⚙️ ⚫️ 🎁 👆 🇨🇻 🈸, 🖼, 🏺, ✳, ♒️. + +## ⚙️ `WSGIMiddleware` + +👆 💪 🗄 `WSGIMiddleware`. + +⤴️ 🎁 🇨🇻 (✅ 🏺) 📱 ⏮️ 🛠️. + +& ⤴️ 🗻 👈 🔽 ➡. + +```Python hl_lines="2-3 22" +{!../../../docs_src/wsgi/tutorial001.py!} +``` + +## ✅ ⚫️ + +🔜, 🔠 📨 🔽 ➡ `/v1/` 🔜 🍵 🏺 🈸. + +& 🎂 🔜 🍵 **FastAPI**. + +🚥 👆 🏃 ⚫️ ⏮️ Uvicorn & 🚶 http://localhost:8000/v1/ 👆 🔜 👀 📨 ⚪️➡️ 🏺: + +```txt +Hello, World from Flask! +``` + +& 🚥 👆 🚶 http://localhost:8000/v2 👆 🔜 👀 📨 ⚪️➡️ FastAPI: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/em/docs/alternatives.md b/docs/em/docs/alternatives.md new file mode 100644 index 0000000000..6169aa52d7 --- /dev/null +++ b/docs/em/docs/alternatives.md @@ -0,0 +1,414 @@ +# 🎛, 🌈 & 🔺 + +⚫️❔ 😮 **FastAPI**, ❔ ⚫️ 🔬 🎏 🎛 & ⚫️❔ ⚫️ 🇭🇲 ⚪️➡️ 👫. + +## 🎶 + +**FastAPI** 🚫🔜 🔀 🚥 🚫 ⏮️ 👷 🎏. + +📤 ✔️ 📚 🧰 ✍ ⏭ 👈 ✔️ ℹ 😮 🚮 🏗. + +👤 ✔️ ❎ 🏗 🆕 🛠️ 📚 1️⃣2️⃣🗓️. 🥇 👤 🔄 ❎ 🌐 ⚒ 📔 **FastAPI** ⚙️ 📚 🎏 🛠️, 🔌-🔌, & 🧰. + +✋️ ☝, 📤 🙅‍♂ 🎏 🎛 🌘 🏗 🕳 👈 🚚 🌐 👫 ⚒, ✊ 🏆 💭 ⚪️➡️ ⏮️ 🧰, & 🌀 👫 🏆 🌌 💪, ⚙️ 🇪🇸 ⚒ 👈 ➖🚫 💪 ⏭ (🐍 3️⃣.6️⃣ ➕ 🆎 🔑). + +## ⏮️ 🧰 + +### + +⚫️ 🌅 🌟 🐍 🛠️ & 🛎 🕴. ⚫️ ⚙️ 🏗 ⚙️ 💖 👱📔. + +⚫️ 📶 😆 🔗 ⏮️ 🔗 💽 (💖 ✳ ⚖️ ✳),, ✔️ ☁ 💽 (💖 🗄, ✳, 👸, ♒️) 👑 🏪 🚒 🚫 📶 ⏩. + +⚫️ ✍ 🏗 🕸 👩‍💻, 🚫 ✍ 🔗 ⚙️ 🏛 🕸 (💖 😥, Vue.js & 📐) ⚖️ 🎏 ⚙️ (💖 📳) 🔗 ⏮️ ⚫️. + +### ✳ 🎂 🛠️ + +✳ 🎂 🛠️ ✍ 🗜 🧰 🏗 🕸 🔗 ⚙️ ✳ 🔘, 📉 🚮 🛠️ 🛠️. + +⚫️ ⚙️ 📚 🏢 ✅ 🦎, 🟥 👒 & 🎟. + +⚫️ 🕐 🥇 🖼 **🏧 🛠️ 🧾**, & 👉 🎯 🕐 🥇 💭 👈 😮 "🔎" **FastAPI**. + +!!! note + ✳ 🎂 🛠️ ✍ ✡ 🇺🇸🏛. 🎏 👼 💃 & Uvicorn, 🔛 ❔ **FastAPI** ⚓️. + + +!!! check "😮 **FastAPI** " + ✔️ 🏧 🛠️ 🧾 🕸 👩‍💻 🔢. + +### 🏺 + +🏺 "🕸", ⚫️ 🚫 🔌 💽 🛠️ 🚫 📚 👜 👈 👟 🔢 ✳. + +👉 🦁 & 💪 ✔ 🔨 👜 💖 ⚙️ ☁ 💽 👑 💽 💾 ⚙️. + +⚫️ 📶 🙅, ⚫️ 📶 🏋️ 💡, 👐 🧾 🤚 🙁 📡 ☝. + +⚫️ 🛎 ⚙️ 🎏 🈸 👈 🚫 🎯 💪 💽, 👩‍💻 🧾, ⚖️ 🙆 📚 ⚒ 👈 👟 🏤-🏗 ✳. 👐 📚 👫 ⚒ 💪 🚮 ⏮️ 🔌-🔌. + +👉 ⚖ 🍕, & ➖ "🕸" 👈 💪 ↔ 📔 ⚫️❔ ⚫️❔ 💪 🔑 ⚒ 👈 👤 💚 🚧. + +👐 🦁 🏺, ⚫️ 😑 💖 👍 🏏 🏗 🔗. ⏭ 👜 🔎 "✳ 🎂 🛠️" 🏺. + +!!! check "😮 **FastAPI** " + ◾-🛠️. ⚒ ⚫️ ⏩ 🌀 & 🏏 🧰 & 🍕 💪. + + ✔️ 🙅 & ⏩ ⚙️ 🕹 ⚙️. + + +### 📨 + +**FastAPI** 🚫 🤙 🎛 **📨**. 👫 ↔ 📶 🎏. + +⚫️ 🔜 🤙 ⚠ ⚙️ 📨 *🔘* FastAPI 🈸. + +✋️, FastAPI 🤚 🌈 ⚪️➡️ 📨. + +**📨** 🗃 *🔗* ⏮️ 🔗 (👩‍💻), ⏪ **FastAPI** 🗃 *🏗* 🔗 (💽). + +👫, 🌖 ⚖️ 🌘, 🔄 🔚, 🔗 🔠 🎏. + +📨 ✔️ 📶 🙅 & 🏋️ 🔧, ⚫️ 📶 ⏩ ⚙️, ⏮️ 🤔 🔢. ✋️ 🎏 🕰, ⚫️ 📶 🏋️ & 🛃. + +👈 ⚫️❔, 💬 🛂 🕸: + +> 📨 1️⃣ 🏆 ⏬ 🐍 📦 🌐 🕰 + +🌌 👆 ⚙️ ⚫️ 📶 🙅. 🖼, `GET` 📨, 👆 🔜 ✍: + +```Python +response = requests.get("http://example.com/some/url") +``` + +FastAPI 😑 🛠️ *➡ 🛠️* 💪 👀 💖: + +```Python hl_lines="1" +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +👀 🔀 `requests.get(...)` & `@app.get(...)`. + +!!! check "😮 **FastAPI** " + * ✔️ 🙅 & 🏋️ 🛠️. + * ⚙️ 🇺🇸🔍 👩‍🔬 📛 (🛠️) 🔗, 🎯 & 🏋️ 🌌. + * ✔️ 🤔 🔢, ✋️ 🏋️ 🛃. + + +### 🦁 / 🗄 + +👑 ⚒ 👤 💚 ⚪️➡️ ✳ 🎂 🛠️ 🏧 🛠️ 🧾. + +⤴️ 👤 🔎 👈 📤 🐩 📄 🔗, ⚙️ 🎻 (⚖️ 📁, ↔ 🎻) 🤙 🦁. + +& 📤 🕸 👩‍💻 🔢 🦁 🛠️ ⏪ ✍. , 💆‍♂ 💪 🏗 🦁 🧾 🛠️ 🔜 ✔ ⚙️ 👉 🕸 👩‍💻 🔢 🔁. + +☝, 🦁 👐 💾 🏛, 📁 🗄. + +👈 ⚫️❔ 🕐❔ 💬 🔃 ⏬ 2️⃣.0️⃣ ⚫️ ⚠ 💬 "🦁", & ⏬ 3️⃣ ➕ "🗄". + +!!! check "😮 **FastAPI** " + 🛠️ & ⚙️ 📂 🐩 🛠️ 🔧, ↩️ 🛃 🔗. + + & 🛠️ 🐩-⚓️ 👩‍💻 🔢 🧰: + + * 🦁 🎚 + * 📄 + + 👫 2️⃣ 👐 ➖ 📶 🌟 & ⚖, ✋️ 🔨 ⏩ 🔎, 👆 💪 🔎 💯 🌖 🎛 👩‍💻 🔢 🗄 (👈 👆 💪 ⚙️ ⏮️ **FastAPI**). + +### 🏺 🎂 🛠️ + +📤 📚 🏺 🎂 🛠️, ✋️ ⏮️ 💰 🕰 & 👷 🔘 🔬 👫, 👤 🔎 👈 📚 😞 ⚖️ 🚫, ⏮️ 📚 🧍 ❔ 👈 ⚒ 👫 🙃. + +### 🍭 + +1️⃣ 👑 ⚒ 💪 🛠️ ⚙️ 📊 "🛠️" ❔ ✊ 📊 ⚪️➡️ 📟 (🐍) & 🏭 ⚫️ 🔘 🕳 👈 💪 📨 🔘 🕸. 🖼, 🏭 🎚 ⚗ 📊 ⚪️➡️ 💽 🔘 🎻 🎚. 🏭 `datetime` 🎚 🔘 🎻, ♒️. + +➕1️⃣ 🦏 ⚒ 💚 🔗 💽 🔬, ⚒ 💭 👈 💽 ☑, 🤝 🎯 🔢. 🖼, 👈 🏑 `int`, & 🚫 🎲 🎻. 👉 ✴️ ⚠ 📨 💽. + +🍵 💽 🔬 ⚙️, 👆 🔜 ✔️ 🌐 ✅ ✋, 📟. + +👫 ⚒ ⚫️❔ 🍭 🏗 🚚. ⚫️ 👑 🗃, & 👤 ✔️ ⚙️ ⚫️ 📚 ⏭. + +✋️ ⚫️ ✍ ⏭ 📤 🔀 🐍 🆎 🔑. , 🔬 🔠 🔗 👆 💪 ⚙️ 🎯 🇨🇻 & 🎓 🚚 🍭. + +!!! check "😮 **FastAPI** " + ⚙️ 📟 🔬 "🔗" 👈 🚚 💽 🆎 & 🔬, 🔁. + +### Webarg + +➕1️⃣ 🦏 ⚒ ✔ 🔗 📊 ⚪️➡️ 📨 📨. + +Webarg 🧰 👈 ⚒ 🚚 👈 🔛 🔝 📚 🛠️, 🔌 🏺. + +⚫️ ⚙️ 🍭 🔘 💽 🔬. & ⚫️ ✍ 🎏 👩‍💻. + +⚫️ 👑 🧰 & 👤 ✔️ ⚙️ ⚫️ 📚 💁‍♂️, ⏭ ✔️ **FastAPI**. + +!!! info + Webarg ✍ 🎏 🍭 👩‍💻. + +!!! check "😮 **FastAPI** " + ✔️ 🏧 🔬 📨 📨 💽. + +### APISpec + +🍭 & Webarg 🚚 🔬, ✍ & 🛠️ 🔌-🔌. + +✋️ 🧾 ❌. ⤴️ APISpec ✍. + +⚫️ 🔌-📚 🛠️ (& 📤 🔌-💃 💁‍♂️). + +🌌 ⚫️ 👷 👈 👆 ✍ 🔑 🔗 ⚙️ 📁 📁 🔘 #️⃣ 🔠 🔢 🚚 🛣. + +& ⚫️ 🏗 🗄 🔗. + +👈 ❔ ⚫️ 👷 🏺, 💃, 🆘, ♒️. + +✋️ ⤴️, 👥 ✔️ 🔄 ⚠ ✔️ ◾-❕, 🔘 🐍 🎻 (🦏 📁). + +👨‍🎨 💪 🚫 ℹ 🌅 ⏮️ 👈. & 🚥 👥 🔀 🔢 ⚖️ 🍭 🔗 & 💭 🔀 👈 📁#️⃣, 🏗 🔗 🔜 ❌. + +!!! info + APISpec ✍ 🎏 🍭 👩‍💻. + + +!!! check "😮 **FastAPI** " + 🐕‍🦺 📂 🐩 🛠️, 🗄. + +### 🏺-Apispec + +⚫️ 🏺 🔌 -, 👈 👔 👯‍♂️ Webarg, 🍭 & APISpec. + +⚫️ ⚙️ ℹ ⚪️➡️ Webarg & 🍭 🔁 🏗 🗄 🔗, ⚙️ APISpec. + +⚫️ 👑 🧰, 📶 🔽-📈. ⚫️ 🔜 🌌 🌖 🌟 🌘 📚 🏺 🔌-🔌 👅 📤. ⚫️ 💪 ↩️ 🚮 🧾 ➖ 💁‍♂️ 🩲 & 📝. + +👉 ❎ ✔️ ✍ 📁 (➕1️⃣ ❕) 🔘 🐍 ✍. + +👉 🌀 🏺, 🏺-Apispec ⏮️ 🍭 & Webarg 👇 💕 👩‍💻 📚 ⏭ 🏗 **FastAPI**. + +⚙️ ⚫️ ↘️ 🏗 📚 🏺 🌕-📚 🚂. 👫 👑 📚 👤 (& 📚 🔢 🏉) ✔️ ⚙️ 🆙 🔜: + +* https://github.com/tiangolo/full-stack +* https://github.com/tiangolo/full-stack-flask-couchbase +* https://github.com/tiangolo/full-stack-flask-couchdb + +& 👫 🎏 🌕-📚 🚂 🧢 [**FastAPI** 🏗 🚂](project-generation.md){.internal-link target=_blank}. + +!!! info + 🏺-Apispec ✍ 🎏 🍭 👩‍💻. + +!!! check "😮 **FastAPI** " + 🏗 🗄 🔗 🔁, ⚪️➡️ 🎏 📟 👈 🔬 🛠️ & 🔬. + +### NestJS (& 📐) + +👉 ➖🚫 🚫 🐍, NestJS 🕸 (📕) ✳ 🛠️ 😮 📐. + +⚫️ 🏆 🕳 🙁 🎏 ⚫️❔ 💪 🔨 ⏮️ 🏺-Apispec. + +⚫️ ✔️ 🛠️ 🔗 💉 ⚙️, 😮 📐 2️⃣. ⚫️ 🚚 🏤-® "💉" (💖 🌐 🎏 🔗 💉 ⚙️ 👤 💭),, ⚫️ 🚮 🎭 & 📟 🔁. + +🔢 🔬 ⏮️ 📕 🆎 (🎏 🐍 🆎 🔑), 👨‍🎨 🐕‍🦺 👍. + +✋️ 📕 📊 🚫 🛡 ⏮️ 📹 🕸, ⚫️ 🚫🔜 ⚓️ 🔛 🆎 🔬 🔬, 🛠️ & 🧾 🎏 🕰. ↩️ 👉 & 🔧 🚫, 🤚 🔬, 🛠️ & 🏧 🔗 ⚡, ⚫️ 💪 🚮 👨‍🎨 📚 🥉. , ⚫️ ▶️️ 🔁. + +⚫️ 💪 🚫 🍵 🔁 🏷 📶 👍. , 🚥 🎻 💪 📨 🎻 🎚 👈 ✔️ 🔘 🏑 👈 🔄 🐦 🎻 🎚, ⚫️ 🚫🔜 ☑ 📄 & ✔. + +!!! check "😮 **FastAPI** " + ⚙️ 🐍 🆎 ✔️ 👑 👨‍🎨 🐕‍🦺. + + ✔️ 🏋️ 🔗 💉 ⚙️. 🔎 🌌 📉 📟 🔁. + +### 🤣 + +⚫️ 🕐 🥇 📶 ⏩ 🐍 🛠️ ⚓️ 🔛 `asyncio`. ⚫️ ⚒ 📶 🎏 🏺. + +!!! note "📡 ℹ" + ⚫️ ⚙️ `uvloop` ↩️ 🔢 🐍 `asyncio` ➰. 👈 ⚫️❔ ⚒ ⚫️ ⏩. + + ⚫️ 🎯 😮 Uvicorn & 💃, 👈 ⏳ ⏩ 🌘 🤣 📂 📇. + +!!! check "😮 **FastAPI** " + 🔎 🌌 ✔️ 😜 🎭. + + 👈 ⚫️❔ **FastAPI** ⚓️ 🔛 💃, ⚫️ ⏩ 🛠️ 💪 (💯 🥉-🥳 📇). + +### 🦅 + +🦅 ➕1️⃣ ↕ 🎭 🐍 🛠️, ⚫️ 🔧 ⭐, & 👷 🏛 🎏 🛠️ 💖 🤗. + +⚫️ 🏗 ✔️ 🔢 👈 📨 2️⃣ 🔢, 1️⃣ "📨" & 1️⃣ "📨". ⤴️ 👆 "✍" 🍕 ⚪️➡️ 📨, & "✍" 🍕 📨. ↩️ 👉 🔧, ⚫️ 🚫 💪 📣 📨 🔢 & 💪 ⏮️ 🐩 🐍 🆎 🔑 🔢 🔢. + +, 💽 🔬, 🛠️, & 🧾, ✔️ ⌛ 📟, 🚫 🔁. ⚖️ 👫 ✔️ 🛠️ 🛠️ 🔛 🔝 🦅, 💖 🤗. 👉 🎏 🔺 🔨 🎏 🛠️ 👈 😮 🦅 🔧, ✔️ 1️⃣ 📨 🎚 & 1️⃣ 📨 🎚 🔢. + +!!! check "😮 **FastAPI** " + 🔎 🌌 🤚 👑 🎭. + + ⤴️ ⏮️ 🤗 (🤗 ⚓️ 🔛 🦅) 😮 **FastAPI** 📣 `response` 🔢 🔢. + + 👐 FastAPI ⚫️ 📦, & ⚙️ ✴️ ⚒ 🎚, 🍪, & 🎛 👔 📟. + +### + +👤 🔎 ♨ 🥇 ▶️ 🏗 **FastAPI**. & ⚫️ ✔️ 🎏 💭: + +* ⚓️ 🔛 🐍 🆎 🔑. +* 🔬 & 🧾 ⚪️➡️ 👫 🆎. +* 🔗 💉 ⚙️. + +⚫️ 🚫 ⚙️ 💽 🔬, 🛠️ & 🧾 🥉-🥳 🗃 💖 Pydantic, ⚫️ ✔️ 🚮 👍. , 👫 💽 🆎 🔑 🔜 🚫 ♻ 💪. + +⚫️ 🚚 🐥 🍖 🌅 🔁 📳. & ⚫️ ⚓️ 🔛 🇨🇻 (↩️ 🔫), ⚫️ 🚫 🔧 ✊ 📈 ↕-🎭 🚚 🧰 💖 Uvicorn, 💃 & 🤣. + +🔗 💉 ⚙️ 🚚 🏤-® 🔗 & 🔗 ❎ 🧢 🔛 📣 🆎. , ⚫️ 🚫 💪 📣 🌅 🌘 1️⃣ "🦲" 👈 🚚 🎯 🆎. + +🛣 📣 👁 🥉, ⚙️ 🔢 📣 🎏 🥉 (↩️ ⚙️ 👨‍🎨 👈 💪 🥉 ▶️️ 🔛 🔝 🔢 👈 🍵 🔗). 👉 🔐 ❔ ✳ 🔨 ⚫️ 🌘 ❔ 🏺 (& 💃) 🔨 ⚫️. ⚫️ 🎏 📟 👜 👈 📶 😆 🔗. + +!!! check "😮 **FastAPI** " + 🔬 ➕ 🔬 💽 🆎 ⚙️ "🔢" 💲 🏷 🔢. 👉 📉 👨‍🎨 🐕‍🦺, & ⚫️ 🚫 💪 Pydantic ⏭. + + 👉 🤙 😮 🛠️ 🍕 Pydantic, 🐕‍🦺 🎏 🔬 📄 👗 (🌐 👉 🛠️ 🔜 ⏪ 💪 Pydantic). + +### 🤗 + +🤗 🕐 🥇 🛠️ 🛠️ 📄 🛠️ 🔢 🆎 ⚙️ 🐍 🆎 🔑. 👉 👑 💭 👈 😮 🎏 🧰 🎏. + +⚫️ ⚙️ 🛃 🆎 🚮 📄 ↩️ 🐩 🐍 🆎, ✋️ ⚫️ 🦏 🔁 ⏩. + +⚫️ 🕐 🥇 🛠️ 🏗 🛃 🔗 📣 🎂 🛠️ 🎻. + +⚫️ 🚫 ⚓️ 🔛 🐩 💖 🗄 & 🎻 🔗. ⚫️ 🚫🔜 🎯 🛠️ ⚫️ ⏮️ 🎏 🧰, 💖 🦁 🎚. ✋️ 🔄, ⚫️ 📶 💡 💭. + +⚫️ ✔️ 😌, ⭐ ⚒: ⚙️ 🎏 🛠️, ⚫️ 💪 ✍ 🔗 & 🇳🇨. + +⚫️ ⚓️ 🔛 ⏮️ 🐩 🔁 🐍 🕸 🛠️ (🇨🇻), ⚫️ 💪 🚫 🍵 *️⃣ & 🎏 👜, 👐 ⚫️ ✔️ ↕ 🎭 💁‍♂️. + +!!! info + 🤗 ✍ ✡ 🗄, 🎏 👼 `isort`, 👑 🧰 🔁 😇 🗄 🐍 📁. + +!!! check "💭 😮 **FastAPI**" + 🤗 😮 🍕 APIStar, & 1️⃣ 🧰 👤 🔎 🏆 👍, 🌟 APIStar. + + 🤗 ℹ 😍 **FastAPI** ⚙️ 🐍 🆎 🔑 📣 🔢, & 🏗 🔗 ⚖ 🛠️ 🔁. + + 🤗 😮 **FastAPI** 📣 `response` 🔢 🔢 ⚒ 🎚 & 🍪. + +### APIStar (<= 0️⃣.5️⃣) + +▶️️ ⏭ 🤔 🏗 **FastAPI** 👤 🔎 **APIStar** 💽. ⚫️ ✔️ 🌖 🌐 👤 👀 & ✔️ 👑 🔧. + +⚫️ 🕐 🥇 🛠️ 🛠️ ⚙️ 🐍 🆎 🔑 📣 🔢 & 📨 👈 👤 ⏱ 👀 (⏭ NestJS & ♨). 👤 🔎 ⚫️ 🌅 ⚖️ 🌘 🎏 🕰 🤗. ✋️ APIStar ⚙️ 🗄 🐩. + +⚫️ ✔️ 🏧 💽 🔬, 💽 🛠️ & 🗄 🔗 ⚡ ⚓️ 🔛 🎏 🆎 🔑 📚 🥉. + +💪 🔗 🔑 🚫 ⚙️ 🎏 🐍 🆎 🔑 💖 Pydantic, ⚫️ 🍖 🌅 🎏 🍭,, 👨‍🎨 🐕‍🦺 🚫🔜 👍, ✋️, APIStar 🏆 💪 🎛. + +⚫️ ✔️ 🏆 🎭 📇 🕰 (🕴 💥 💃). + +🥇, ⚫️ 🚫 ✔️ 🏧 🛠️ 🧾 🕸 🎚, ✋️ 👤 💭 👤 💪 🚮 🦁 🎚 ⚫️. + +⚫️ ✔️ 🔗 💉 ⚙️. ⚫️ ✔ 🏤-® 🦲, 🎏 🧰 🔬 🔛. ✋️, ⚫️ 👑 ⚒. + +👤 🙅 💪 ⚙️ ⚫️ 🌕 🏗, ⚫️ 🚫 ✔️ 💂‍♂ 🛠️,, 👤 🚫 🚫 ❎ 🌐 ⚒ 👤 ✔️ ⏮️ 🌕-📚 🚂 ⚓️ 🔛 🏺-Apispec. 👤 ✔️ 👇 📈 🏗 ✍ 🚲 📨 ❎ 👈 🛠️. + +✋️ ⤴️, 🏗 🎯 🔀. + +⚫️ 🙅‍♂ 📏 🛠️ 🕸 🛠️, 👼 💪 🎯 🔛 💃. + +🔜 APIStar ⚒ 🧰 ✔ 🗄 🔧, 🚫 🕸 🛠️. + +!!! info + APIStar ✍ ✡ 🇺🇸🏛. 🎏 👨 👈 ✍: + + * ✳ 🎂 🛠️ + * 💃 (❔ **FastAPI** ⚓️) + * Uvicorn (⚙️ 💃 & **FastAPI**) + +!!! check "😮 **FastAPI** " + 🔀. + + 💭 📣 💗 👜 (💽 🔬, 🛠️ & 🧾) ⏮️ 🎏 🐍 🆎, 👈 🎏 🕰 🚚 👑 👨‍🎨 🐕‍🦺, 🕳 👤 🤔 💎 💭. + + & ⏮️ 🔎 📏 🕰 🎏 🛠️ & 🔬 📚 🎏 🎛, APIStar 🏆 🎛 💪. + + ⤴️ APIStar ⛔️ 🔀 💽 & 💃 ✍, & 🆕 👻 🏛 ✅ ⚙️. 👈 🏁 🌈 🏗 **FastAPI**. + + 👤 🤔 **FastAPI** "🛐 👨‍💼" APIStar, ⏪ 📉 & 📈 ⚒, ⌨ ⚙️, & 🎏 🍕, ⚓️ 🔛 🏫 ⚪️➡️ 🌐 👉 ⏮️ 🧰. + +## ⚙️ **FastAPI** + +### Pydantic + +Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 🐍 🆎 🔑. + +👈 ⚒ ⚫️ 📶 🏋️. + +⚫️ ⭐ 🍭. 👐 ⚫️ ⏩ 🌘 🍭 📇. & ⚫️ ⚓️ 🔛 🎏 🐍 🆎 🔑, 👨‍🎨 🐕‍🦺 👑. + +!!! check "**FastAPI** ⚙️ ⚫️" + 🍵 🌐 💽 🔬, 💽 🛠️ & 🏧 🏷 🧾 (⚓️ 🔛 🎻 🔗). + + **FastAPI** ⤴️ ✊ 👈 🎻 🔗 💽 & 🚮 ⚫️ 🗄, ↖️ ⚪️➡️ 🌐 🎏 👜 ⚫️ 🔨. + +### 💃 + +💃 💿 🔫 🛠️/🧰, ❔ 💯 🏗 ↕-🎭 ✳ 🐕‍🦺. + +⚫️ 📶 🙅 & 🏋️. ⚫️ 🔧 💪 🏧, & ✔️ 🔧 🦲. + +⚫️ ✔️: + +* 🤙 🎆 🎭. +* *️⃣ 🐕‍🦺. +* -🛠️ 🖥 📋. +* 🕴 & 🤫 🎉. +* 💯 👩‍💻 🏗 🔛 🇸🇲. +* ⚜, 🗜, 🎻 📁, 🎏 📨. +* 🎉 & 🍪 🐕‍🦺. +* 1️⃣0️⃣0️⃣ 💯 💯 💰. +* 1️⃣0️⃣0️⃣ 💯 🆎 ✍ ✍. +* 👩‍❤‍👨 🏋️ 🔗. + +💃 ⏳ ⏩ 🐍 🛠️ 💯. 🕴 💥 Uvicorn, ❔ 🚫 🛠️, ✋️ 💽. + +💃 🚚 🌐 🔰 🕸 🕸 🛠️. + +✋️ ⚫️ 🚫 🚚 🏧 💽 🔬, 🛠️ ⚖️ 🧾. + +👈 1️⃣ 👑 👜 👈 **FastAPI** 🚮 🔛 🔝, 🌐 ⚓️ 🔛 🐍 🆎 🔑 (⚙️ Pydantic). 👈, ➕ 🔗 💉 ⚙️, 💂‍♂ 🚙, 🗄 🔗 ⚡, ♒️. + +!!! note "📡 ℹ" + 🔫 🆕 "🐩" ➖ 🛠️ ✳ 🐚 🏉 👨‍🎓. ⚫️ 🚫 "🐍 🐩" (🇩🇬), 👐 👫 🛠️ 🔨 👈. + + 👐, ⚫️ ⏪ ➖ ⚙️ "🐩" 📚 🧰. 👉 📉 📉 🛠️, 👆 💪 🎛 Uvicorn 🙆 🎏 🔫 💽 (💖 👸 ⚖️ Hypercorn), ⚖️ 👆 💪 🚮 🔫 🔗 🧰, 💖 `python-socketio`. + +!!! check "**FastAPI** ⚙️ ⚫️" + 🍵 🌐 🐚 🕸 🍕. ❎ ⚒ 🔛 🔝. + + 🎓 `FastAPI` ⚫️ 😖 🔗 ⚪️➡️ 🎓 `Starlette`. + + , 🕳 👈 👆 💪 ⏮️ 💃, 👆 💪 ⚫️ 🔗 ⏮️ **FastAPI**, ⚫️ 🌖 💃 🔛 💊. + +### Uvicorn + +Uvicorn 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool. + +⚫️ 🚫 🕸 🛠️, ✋️ 💽. 🖼, ⚫️ 🚫 🚚 🧰 🕹 ➡. 👈 🕳 👈 🛠️ 💖 💃 (⚖️ **FastAPI**) 🔜 🚚 🔛 🔝. + +⚫️ 👍 💽 💃 & **FastAPI**. + +!!! check "**FastAPI** 👍 ⚫️" + 👑 🕸 💽 🏃 **FastAPI** 🈸. + + 👆 💪 🌀 ⚫️ ⏮️ 🐁, ✔️ 🔁 👁-🛠️ 💽. + + ✅ 🌅 ℹ [🛠️](deployment/index.md){.internal-link target=_blank} 📄. + +## 📇 & 🚅 + +🤔, 🔬, & 👀 🔺 🖖 Uvicorn, 💃 & FastAPI, ✅ 📄 🔃 [📇](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/em/docs/async.md b/docs/em/docs/async.md new file mode 100644 index 0000000000..13b362b5de --- /dev/null +++ b/docs/em/docs/async.md @@ -0,0 +1,430 @@ +# 🛠️ & 🔁 / ⌛ + +ℹ 🔃 `async def` ❕ *➡ 🛠️ 🔢* & 🖥 🔃 🔁 📟, 🛠️, & 🔁. + +## 🏃 ❓ + +🆑;👩‍⚕️: + +🚥 👆 ⚙️ 🥉 🥳 🗃 👈 💬 👆 🤙 👫 ⏮️ `await`, 💖: + +```Python +results = await some_library() +``` + +⤴️, 📣 👆 *➡ 🛠️ 🔢* ⏮️ `async def` 💖: + +```Python hl_lines="2" +@app.get('/') +async def read_results(): + results = await some_library() + return results +``` + +!!! note + 👆 💪 🕴 ⚙️ `await` 🔘 🔢 ✍ ⏮️ `async def`. + +--- + +🚥 👆 ⚙️ 🥉 🥳 🗃 👈 🔗 ⏮️ 🕳 (💽, 🛠️, 📁 ⚙️, ♒️.) & 🚫 ✔️ 🐕‍🦺 ⚙️ `await`, (👉 ⏳ 💼 🌅 💽 🗃), ⤴️ 📣 👆 *➡ 🛠️ 🔢* 🛎, ⏮️ `def`, 💖: + +```Python hl_lines="2" +@app.get('/') +def results(): + results = some_library() + return results +``` + +--- + +🚥 👆 🈸 (😫) 🚫 ✔️ 🔗 ⏮️ 🕳 🙆 & ⌛ ⚫️ 📨, ⚙️ `async def`. + +--- + +🚥 👆 🚫 💭, ⚙️ 😐 `def`. + +--- + +**🗒**: 👆 💪 🌀 `def` & `async def` 👆 *➡ 🛠️ 🔢* 🌅 👆 💪 & 🔬 🔠 1️⃣ ⚙️ 🏆 🎛 👆. FastAPI 🔜 ▶️️ 👜 ⏮️ 👫. + +😆, 🙆 💼 🔛, FastAPI 🔜 👷 🔁 & 📶 ⏩. + +✋️ 📄 📶 🔛, ⚫️ 🔜 💪 🎭 🛠️. + +## 📡 ℹ + +🏛 ⏬ 🐍 ✔️ 🐕‍🦺 **"🔁 📟"** ⚙️ 🕳 🤙 **"🔁"**, ⏮️ **`async` & `await`** ❕. + +➡️ 👀 👈 🔤 🍕 📄 🔛: + +* **🔁 📟** +* **`async` & `await`** +* **🔁** + +## 🔁 📟 + +🔁 📟 ⛓ 👈 🇪🇸 👶 ✔️ 🌌 💬 💻 / 📋 👶 👈 ☝ 📟, ⚫️ 👶 🔜 ✔️ ⌛ *🕳 🙆* 🏁 👱 🙆. ➡️ 💬 👈 *🕳 🙆* 🤙 "🐌-📁" 👶. + +, ⏮️ 👈 🕰, 💻 💪 🚶 & 🎏 👷, ⏪ "🐌-📁" 👶 🏁. + +⤴️ 💻 / 📋 👶 🔜 👟 🔙 🔠 🕰 ⚫️ ✔️ 🤞 ↩️ ⚫️ ⌛ 🔄, ⚖️ 🕐❔ ⚫️ 👶 🏁 🌐 👷 ⚫️ ✔️ 👈 ☝. & ⚫️ 👶 🔜 👀 🚥 🙆 📋 ⚫️ ⌛ ✔️ ⏪ 🏁, 🤸 ⚫️❔ ⚫️ ✔️. + +⏭, ⚫️ 👶 ✊ 🥇 📋 🏁 (➡️ 💬, 👆 "🐌-📁" 👶) & 😣 ⚫️❔ ⚫️ ✔️ ⏮️ ⚫️. + +👈 "⌛ 🕳 🙆" 🛎 🔗 👤/🅾 🛠️ 👈 📶 "🐌" (🔬 🚅 🕹 & 💾 💾), 💖 ⌛: + +* 📊 ⚪️➡️ 👩‍💻 📨 🔘 🕸 +* 📊 📨 👆 📋 📨 👩‍💻 🔘 🕸 +* 🎚 📁 💾 ✍ ⚙️ & 🤝 👆 📋 +* 🎚 👆 📋 🤝 ⚙️ ✍ 💾 +* 🛰 🛠️ 🛠️ +* 💽 🛠️ 🏁 +* 💽 🔢 📨 🏁 +* ♒️. + +🛠️ 🕰 🍴 ✴️ ⌛ 👤/🅾 🛠️, 👫 🤙 👫 "👤/🅾 🔗" 🛠️. + +⚫️ 🤙 "🔁" ↩️ 💻 / 📋 🚫 ✔️ "🔁" ⏮️ 🐌 📋, ⌛ ☑ 🙍 👈 📋 🏁, ⏪ 🔨 🕳, 💪 ✊ 📋 🏁 & 😣 👷. + +↩️ 👈, 💆‍♂ "🔁" ⚙️, 🕐 🏁, 📋 💪 ⌛ ⏸ 🐥 👄 (⏲) 💻 / 📋 🏁 ⚫️❔ ⚫️ 🚶, & ⤴️ 👟 🔙 ✊ 🏁 & 😣 👷 ⏮️ 👫. + +"🔁" (👽 "🔁") 👫 🛎 ⚙️ ⚖ "🔁", ↩️ 💻 / 📋 ⏩ 🌐 📶 🔁 ⏭ 🔀 🎏 📋, 🚥 👈 🔁 🔌 ⌛. + +### 🛠️ & 🍔 + +👉 💭 **🔁** 📟 🔬 🔛 🕣 🤙 **"🛠️"**. ⚫️ 🎏 ⚪️➡️ **"🔁"**. + +**🛠️** & **🔁** 👯‍♂️ 🔗 "🎏 👜 😥 🌅 ⚖️ 🌘 🎏 🕰". + +✋️ ℹ 🖖 *🛠️* & *🔁* 🎏. + +👀 🔺, 🌈 📄 📖 🔃 🍔: + +### 🛠️ 🍔 + +👆 🚶 ⏮️ 👆 🥰 🤚 ⏩ 🥕, 👆 🧍 ⏸ ⏪ 🏧 ✊ ✔ ⚪️➡️ 👫👫 🚪 👆. 👶 + + + +⤴️ ⚫️ 👆 🔄, 👆 🥉 👆 ✔ 2️⃣ 📶 🎀 🍔 👆 🥰 & 👆. 👶 👶 + + + +🏧 💬 🕳 🍳 👨‍🍳 👫 💭 👫 ✔️ 🏗 👆 🍔 (✋️ 👫 ⏳ 🏗 🕐 ⏮️ 👩‍💻). + + + +👆 💸. 👶 + +🏧 🤝 👆 🔢 👆 🔄. + + + +⏪ 👆 ⌛, 👆 🚶 ⏮️ 👆 🥰 & ⚒ 🏓, 👆 🧎 & 💬 ⏮️ 👆 🥰 📏 🕰 (👆 🍔 📶 🎀 & ✊ 🕰 🏗). + +👆 🏖 🏓 ⏮️ 👆 🥰, ⏪ 👆 ⌛ 🍔, 👆 💪 💸 👈 🕰 😮 ❔ 👌, 🐨 & 🙃 👆 🥰 👶 👶 👶. + + + +⏪ ⌛ & 💬 👆 🥰, ⚪️➡️ 🕰 🕰, 👆 ✅ 🔢 🖥 🔛 ⏲ 👀 🚥 ⚫️ 👆 🔄 ⏪. + +⤴️ ☝, ⚫️ 😒 👆 🔄. 👆 🚶 ⏲, 🤚 👆 🍔 & 👟 🔙 🏓. + + + +👆 & 👆 🥰 🍴 🍔 & ✔️ 👌 🕰. 👶 + + + +!!! info + 🌹 🖼 👯 🍏. 👶 + +--- + +🌈 👆 💻 / 📋 👶 👈 📖. + +⏪ 👆 ⏸, 👆 ⛽ 👶, ⌛ 👆 🔄, 🚫 🔨 🕳 📶 "😌". ✋️ ⏸ ⏩ ↩️ 🏧 🕴 ✊ ✔ (🚫 🏗 👫), 👈 👌. + +⤴️, 🕐❔ ⚫️ 👆 🔄, 👆 ☑ "😌" 👷, 👆 🛠️ 🍣, 💭 ⚫️❔ 👆 💚, 🤚 👆 🥰 ⚒, 💸, ✅ 👈 👆 🤝 ☑ 💵 ⚖️ 💳, ✅ 👈 👆 🈚 ☑, ✅ 👈 ✔ ✔️ ☑ 🏬, ♒️. + +✋️ ⤴️, ✋️ 👆 🚫 ✔️ 👆 🍔, 👆 👷 ⏮️ 🏧 "🔛 ⏸" ⏸, ↩️ 👆 ✔️ ⌛ 👶 👆 🍔 🔜. + +✋️ 👆 🚶 ↖️ ⚪️➡️ ⏲ & 🧎 🏓 ⏮️ 🔢 👆 🔄, 👆 💪 🎛 👶 👆 🙋 👆 🥰, & "👷" 👶 👶 🔛 👈. ⤴️ 👆 🔄 🔨 🕳 📶 "😌" 😏 ⏮️ 👆 🥰 👶. + +⤴️ 🏧 👶 💬 "👤 🏁 ⏮️ 🔨 🍔" 🚮 👆 🔢 🔛 ⏲ 🖥, ✋️ 👆 🚫 🦘 💖 😜 ⏪ 🕐❔ 🖥 🔢 🔀 👆 🔄 🔢. 👆 💭 🙅‍♂ 1️⃣ 🔜 📎 👆 🍔 ↩️ 👆 ✔️ 🔢 👆 🔄, & 👫 ✔️ 👫. + +👆 ⌛ 👆 🥰 🏁 📖 (🏁 ⏮️ 👷 👶 / 📋 ➖ 🛠️ 👶), 😀 🖐 & 💬 👈 👆 🔜 🍔 ⏸. + +⤴️ 👆 🚶 ⏲ 👶, ▶️ 📋 👈 🔜 🏁 👶, ⚒ 🍔, 💬 👏 & ✊ 👫 🏓. 👈 🏁 👈 🔁 / 📋 🔗 ⏮️ ⏲ ⏹. 👈 🔄, ✍ 🆕 📋, "🍴 🍔" 👶 👶, ✋️ ⏮️ 1️⃣ "🤚 🍔" 🏁 ⏹. + +### 🔗 🍔 + +🔜 ➡️ 🌈 👫 ➖🚫 🚫 "🛠️ 🍔", ✋️ "🔗 🍔". + +👆 🚶 ⏮️ 👆 🥰 🤚 🔗 ⏩ 🥕. + +👆 🧍 ⏸ ⏪ 📚 (➡️ 💬 8️⃣) 🏧 👈 🎏 🕰 🍳 ✊ ✔ ⚪️➡️ 👫👫 🚪 👆. + +👱 ⏭ 👆 ⌛ 👫 🍔 🔜 ⏭ 🍂 ⏲ ↩️ 🔠 8️⃣ 🏧 🚶 & 🏗 🍔 ▶️️ ↖️ ⏭ 💆‍♂ ⏭ ✔. + + + +⤴️ ⚫️ 😒 👆 🔄, 👆 🥉 👆 ✔ 2️⃣ 📶 🎀 🍔 👆 🥰 & 👆. + +👆 💸 👶. + + + +🏧 🚶 👨‍🍳. + +👆 ⌛, 🧍 🚪 ⏲ 👶, 👈 🙅‍♂ 1️⃣ 🙆 ✊ 👆 🍔 ⏭ 👆, 📤 🙅‍♂ 🔢 🔄. + + + +👆 & 👆 🥰 😩 🚫 ➡️ 🙆 🤚 🚪 👆 & ✊ 👆 🍔 🕐❔ 👫 🛬, 👆 🚫🔜 💸 🙋 👆 🥰. 👶 + +👉 "🔁" 👷, 👆 "🔁" ⏮️ 🏧/🍳 👶 👶. 👆 ✔️ ⌛ 👶 & 📤 ☑ 🙍 👈 🏧/🍳 👶 👶 🏁 🍔 & 🤝 👫 👆, ⚖️ ⏪, 👱 🙆 💪 ✊ 👫. + + + +⤴️ 👆 🏧/🍳 👶 👶 😒 👟 🔙 ⏮️ 👆 🍔, ⏮️ 📏 🕰 ⌛ 👶 📤 🚪 ⏲. + + + +👆 ✊ 👆 🍔 & 🚶 🏓 ⏮️ 👆 🥰. + +👆 🍴 👫, & 👆 🔨. ⏹ + + + +📤 🚫 🌅 💬 ⚖️ 😏 🌅 🕰 💸 ⌛ 👶 🚪 ⏲. 👶 + +!!! info + 🌹 🖼 👯 🍏. 👶 + +--- + +👉 😐 🔗 🍔, 👆 💻 / 📋 👶 ⏮️ 2️⃣ 🕹 (👆 & 👆 🥰), 👯‍♂️ ⌛ 👶 & 💡 👫 🙋 👶 "⌛ 🔛 ⏲" 👶 📏 🕰. + +⏩ 🥕 🏪 ✔️ 8️⃣ 🕹 (🏧/🍳). ⏪ 🛠️ 🍔 🏪 💪 ✔️ ✔️ 🕴 2️⃣ (1️⃣ 🏧 & 1️⃣ 🍳). + +✋️, 🏁 💡 🚫 🏆. 👶 + +--- + +👉 🔜 🔗 🌓 📖 🍔. 👶 + +🌅 "🎰 👨‍❤‍👨" 🖼 👉, 🌈 🏦. + +🆙 ⏳, 🏆 🏦 ✔️ 💗 🏧 👶 👶 👶 👶 👶 👶 👶 👶 & 🦏 ⏸ 👶 👶 👶 👶 👶 👶 👶 👶. + +🌐 🏧 🔨 🌐 👷 ⏮️ 1️⃣ 👩‍💻 ⏮️ 🎏 👶 👶 👶. + +& 👆 ✔️ ⌛ 👶 ⏸ 📏 🕰 ⚖️ 👆 💸 👆 🔄. + +👆 🎲 🚫🔜 💚 ✊ 👆 🥰 👶 ⏮️ 👆 👷 🏦 👶. + +### 🍔 🏁 + +👉 😐 "⏩ 🥕 🍔 ⏮️ 👆 🥰", 📤 📚 ⌛ 👶, ⚫️ ⚒ 📚 🌅 🔑 ✔️ 🛠️ ⚙️ ⏸ 👶 👶. + +👉 💼 🌅 🕸 🈸. + +📚, 📚 👩‍💻, ✋️ 👆 💽 ⌛ 👶 👫 🚫--👍 🔗 📨 👫 📨. + +& ⤴️ ⌛ 👶 🔄 📨 👟 🔙. + +👉 "⌛" 👶 ⚖ ⏲, ✋️, ⚖ ⚫️ 🌐, ⚫️ 📚 ⌛ 🔚. + +👈 ⚫️❔ ⚫️ ⚒ 📚 🔑 ⚙️ 🔁 ⏸ 👶 👶 📟 🕸 🔗. + +👉 😇 🔀 ⚫️❔ ⚒ ✳ 🌟 (✋️ ✳ 🚫 🔗) & 👈 💪 🚶 🛠️ 🇪🇸. + +& 👈 🎏 🎚 🎭 👆 🤚 ⏮️ **FastAPI**. + +& 👆 💪 ✔️ 🔁 & 🔀 🎏 🕰, 👆 🤚 ↕ 🎭 🌘 🌅 💯 ✳ 🛠️ & 🔛 🇷🇪 ⏮️ 🚶, ❔ ✍ 🇪🇸 🔐 🅱 (🌐 👏 💃). + +### 🛠️ 👍 🌘 🔁 ❓ + +😆 ❗ 👈 🚫 🛐 📖. + +🛠️ 🎏 🌘 🔁. & ⚫️ 👻 🔛 **🎯** 😐 👈 🔌 📚 ⌛. ↩️ 👈, ⚫️ 🛎 📚 👍 🌘 🔁 🕸 🈸 🛠️. ✋️ 🚫 🌐. + +, ⚖ 👈 👅, 🌈 📄 📏 📖: + +> 👆 ✔️ 🧹 🦏, 💩 🏠. + +*😆, 👈 🎂 📖*. + +--- + +📤 🙅‍♂ ⌛ 👶 🙆, 📚 👷 🔨, 🔛 💗 🥉 🏠. + +👆 💪 ✔️ 🔄 🍔 🖼, 🥇 🏠 🧖‍♂, ⤴️ 👨‍🍳, ✋️ 👆 🚫 ⌛ 👶 🕳, 🧹 & 🧹, 🔄 🚫🔜 📉 🕳. + +⚫️ 🔜 ✊ 🎏 💸 🕰 🏁 ⏮️ ⚖️ 🍵 🔄 (🛠️) & 👆 🔜 ✔️ ⌛ 🎏 💸 👷. + +✋️ 👉 💼, 🚥 👆 💪 ✊️ 8️⃣ 👰-🏧/🍳/🔜-🧹, & 🔠 1️⃣ 👫 (➕ 👆) 💪 ✊ 🏒 🏠 🧹 ⚫️, 👆 💪 🌐 👷 **🔗**, ⏮️ ➕ ℹ, & 🏁 🌅 🔜. + +👉 😐, 🔠 1️⃣ 🧹 (🔌 👆) 🔜 🕹, 🤸 👫 🍕 👨‍🏭. + +& 🏆 🛠️ 🕰 ✊ ☑ 👷 (↩️ ⌛), & 👷 💻 ⌛ 💽, 👫 🤙 👫 ⚠ "💽 🎁". + +--- + +⚠ 🖼 💽 🔗 🛠️ 👜 👈 🚚 🏗 🧪 🏭. + +🖼: + +* **🎧** ⚖️ **🖼 🏭**. +* **💻 👓**: 🖼 ✍ 💯 🔅, 🔠 🔅 ✔️ 3️⃣ 💲 / 🎨, 🏭 👈 🛎 🚚 💻 🕳 🔛 📚 🔅, 🌐 🎏 🕰. +* **🎰 🏫**: ⚫️ 🛎 🚚 📚 "✖" & "🖼" ✖. 💭 🦏 📋 ⏮️ 🔢 & ✖ 🌐 👫 👯‍♂️ 🎏 🕰. +* **⏬ 🏫**: 👉 🎧-🏑 🎰 🏫,, 🎏 ✔. ⚫️ 👈 📤 🚫 👁 📋 🔢 ✖, ✋️ 🦏 ⚒ 👫, & 📚 💼, 👆 ⚙️ 🎁 🕹 🏗 & / ⚖️ ⚙️ 👈 🏷. + +### 🛠️ ➕ 🔁: 🕸 ➕ 🎰 🏫 + +⏮️ **FastAPI** 👆 💪 ✊ 📈 🛠️ 👈 📶 ⚠ 🕸 🛠️ (🎏 👑 🧲 ✳). + +✋️ 👆 💪 🐄 💰 🔁 & 💾 (✔️ 💗 🛠️ 🏃‍♂ 🔗) **💽 🎁** ⚖ 💖 👈 🎰 🏫 ⚙️. + +👈, ➕ 🙅 👐 👈 🐍 👑 🇪🇸 **💽 🧪**, 🎰 🏫 & ✴️ ⏬ 🏫, ⚒ FastAPI 📶 👍 🏏 💽 🧪 / 🎰 🏫 🕸 🔗 & 🈸 (👪 📚 🎏). + +👀 ❔ 🏆 👉 🔁 🏭 👀 📄 🔃 [🛠️](deployment/index.md){.internal-link target=_blank}. + +## `async` & `await` + +🏛 ⏬ 🐍 ✔️ 📶 🏋️ 🌌 🔬 🔁 📟. 👉 ⚒ ⚫️ 👀 💖 😐 "🔁" 📟 & "⌛" 👆 ▶️️ 🙍. + +🕐❔ 📤 🛠️ 👈 🔜 🚚 ⌛ ⏭ 🤝 🏁 & ✔️ 🐕‍🦺 👉 🆕 🐍 ⚒, 👆 💪 📟 ⚫️ 💖: + +```Python +burgers = await get_burgers(2) +``` + +🔑 📥 `await`. ⚫️ 💬 🐍 👈 ⚫️ ✔️ ⌛ ⏸ `get_burgers(2)` 🏁 🔨 🚮 👜 👶 ⏭ ♻ 🏁 `burgers`. ⏮️ 👈, 🐍 🔜 💭 👈 ⚫️ 💪 🚶 & 🕳 🙆 👶 👶 👐 (💖 📨 ➕1️⃣ 📨). + +`await` 👷, ⚫️ ✔️ 🔘 🔢 👈 🐕‍🦺 👉 🔀. 👈, 👆 📣 ⚫️ ⏮️ `async def`: + +```Python hl_lines="1" +async def get_burgers(number: int): + # Do some asynchronous stuff to create the burgers + return burgers +``` + +...↩️ `def`: + +```Python hl_lines="2" +# This is not asynchronous +def get_sequential_burgers(number: int): + # Do some sequential stuff to create the burgers + return burgers +``` + +⏮️ `async def`, 🐍 💭 👈, 🔘 👈 🔢, ⚫️ ✔️ 🤔 `await` 🧬, & 👈 ⚫️ 💪 "⏸" ⏸ 🛠️ 👈 🔢 & 🚶 🕳 🙆 👶 ⏭ 👟 🔙. + +🕐❔ 👆 💚 🤙 `async def` 🔢, 👆 ✔️ "⌛" ⚫️. , 👉 🏆 🚫 👷: + +```Python +# This won't work, because get_burgers was defined with: async def +burgers = get_burgers(2) +``` + +--- + +, 🚥 👆 ⚙️ 🗃 👈 💬 👆 👈 👆 💪 🤙 ⚫️ ⏮️ `await`, 👆 💪 ✍ *➡ 🛠️ 🔢* 👈 ⚙️ ⚫️ ⏮️ `async def`, 💖: + +```Python hl_lines="2-3" +@app.get('/burgers') +async def read_burgers(): + burgers = await get_burgers(2) + return burgers +``` + +### 🌅 📡 ℹ + +👆 💪 ✔️ 👀 👈 `await` 💪 🕴 ⚙️ 🔘 🔢 🔬 ⏮️ `async def`. + +✋️ 🎏 🕰, 🔢 🔬 ⏮️ `async def` ✔️ "⌛". , 🔢 ⏮️ `async def` 💪 🕴 🤙 🔘 🔢 🔬 ⏮️ `async def` 💁‍♂️. + +, 🔃 🥚 & 🐔, ❔ 👆 🤙 🥇 `async` 🔢 ❓ + +🚥 👆 👷 ⏮️ **FastAPI** 👆 🚫 ✔️ 😟 🔃 👈, ↩️ 👈 "🥇" 🔢 🔜 👆 *➡ 🛠️ 🔢*, & FastAPI 🔜 💭 ❔ ▶️️ 👜. + +✋️ 🚥 👆 💚 ⚙️ `async` / `await` 🍵 FastAPI, 👆 💪 ⚫️ 👍. + +### ✍ 👆 👍 🔁 📟 + +💃 (& **FastAPI**) ⚓️ 🔛 AnyIO, ❔ ⚒ ⚫️ 🔗 ⏮️ 👯‍♂️ 🐍 🐩 🗃 & 🎻. + +🎯, 👆 💪 🔗 ⚙️ AnyIO 👆 🏧 🛠️ ⚙️ 💼 👈 🚚 🌅 🏧 ⚓ 👆 👍 📟. + +& 🚥 👆 🚫 ⚙️ FastAPI, 👆 💪 ✍ 👆 👍 🔁 🈸 ⏮️ AnyIO 🏆 🔗 & 🤚 🚮 💰 (✅ *📊 🛠️*). + +### 🎏 📨 🔁 📟 + +👉 👗 ⚙️ `async` & `await` 📶 🆕 🇪🇸. + +✋️ ⚫️ ⚒ 👷 ⏮️ 🔁 📟 📚 ⏩. + +👉 🎏 ❕ (⚖️ 🌖 🌓) 🔌 ⏳ 🏛 ⏬ 🕸 (🖥 & ✳). + +✋️ ⏭ 👈, 🚚 🔁 📟 🌖 🏗 & ⚠. + +⏮️ ⏬ 🐍, 👆 💪 ✔️ ⚙️ 🧵 ⚖️ 🐁. ✋️ 📟 🌌 🌖 🏗 🤔, ℹ, & 💭 🔃. + +⏮️ ⏬ ✳ / 🖥 🕸, 👆 🔜 ✔️ ⚙️ "⏲". ❔ ↘️ ⏲ 🔥😈. + +## 🔁 + +**🔁** 📶 🎀 ⚖ 👜 📨 `async def` 🔢. 🐍 💭 👈 ⚫️ 🕳 💖 🔢 👈 ⚫️ 💪 ▶️ & 👈 ⚫️ 🔜 🔚 ☝, ✋️ 👈 ⚫️ 5️⃣📆 ⏸ ⏸ 🔘 💁‍♂️, 🕐❔ 📤 `await` 🔘 ⚫️. + +✋️ 🌐 👉 🛠️ ⚙️ 🔁 📟 ⏮️ `async` & `await` 📚 🕰 🔬 ⚙️ "🔁". ⚫️ ⭐ 👑 🔑 ⚒ 🚶, "🔁". + +## 🏁 + +➡️ 👀 🎏 🔤 ⚪️➡️ 🔛: + +> 🏛 ⏬ 🐍 ✔️ 🐕‍🦺 **"🔁 📟"** ⚙️ 🕳 🤙 **"🔁"**, ⏮️ **`async` & `await`** ❕. + +👈 🔜 ⚒ 🌅 🔑 🔜. 👶 + +🌐 👈 ⚫️❔ 🏋️ FastAPI (🔘 💃) & ⚫️❔ ⚒ ⚫️ ✔️ ✅ 🎆 🎭. + +## 📶 📡 ℹ + +!!! warning + 👆 💪 🎲 🚶 👉. + + 👉 📶 📡 ℹ ❔ **FastAPI** 👷 🔘. + + 🚥 👆 ✔️ 📡 💡 (🈶-🏋, 🧵, 🍫, ♒️.) & 😟 🔃 ❔ FastAPI 🍵 `async def` 🆚 😐 `def`, 🚶 ⤴️. + +### ➡ 🛠️ 🔢 + +🕐❔ 👆 📣 *➡ 🛠️ 🔢* ⏮️ 😐 `def` ↩️ `async def`, ⚫️ 🏃 🔢 🧵 👈 ⤴️ ⌛, ↩️ ➖ 🤙 🔗 (⚫️ 🔜 🍫 💽). + +🚥 👆 👟 ⚪️➡️ ➕1️⃣ 🔁 🛠️ 👈 🔨 🚫 👷 🌌 🔬 🔛 & 👆 ⚙️ ⚖ 🙃 📊-🕴 *➡ 🛠️ 🔢* ⏮️ ✅ `def` 🤪 🎭 📈 (🔃 1️⃣0️⃣0️⃣ 💓), 🙏 🗒 👈 **FastAPI** ⭐ 🔜 🔄. 👫 💼, ⚫️ 👻 ⚙️ `async def` 🚥 👆 *➡ 🛠️ 🔢* ⚙️ 📟 👈 🎭 🚧 👤/🅾. + +, 👯‍♂️ ⚠, 🤞 👈 **FastAPI** 🔜 [⏩](/#performance){.internal-link target=_blank} 🌘 (⚖️ 🌘 ⭐) 👆 ⏮️ 🛠️. + +### 🔗 + +🎏 ✔ [🔗](/tutorial/dependencies/index.md){.internal-link target=_blank}. 🚥 🔗 🐩 `def` 🔢 ↩️ `async def`, ⚫️ 🏃 🔢 🧵. + +### 🎧-🔗 + +👆 💪 ✔️ 💗 🔗 & [🎧-🔗](/tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} 🚫 🔠 🎏 (🔢 🔢 🔑), 👫 💪 ✍ ⏮️ `async def` & ⏮️ 😐 `def`. ⚫️ 🔜 👷, & 🕐 ✍ ⏮️ 😐 `def` 🔜 🤙 🔛 🔢 🧵 (⚪️➡️ 🧵) ↩️ ➖ "⌛". + +### 🎏 🚙 🔢 + +🙆 🎏 🚙 🔢 👈 👆 🤙 🔗 💪 ✍ ⏮️ 😐 `def` ⚖️ `async def` & FastAPI 🏆 🚫 📉 🌌 👆 🤙 ⚫️. + +👉 🔅 🔢 👈 FastAPI 🤙 👆: *➡ 🛠️ 🔢* & 🔗. + +🚥 👆 🚙 🔢 😐 🔢 ⏮️ `def`, ⚫️ 🔜 🤙 🔗 (👆 ✍ ⚫️ 👆 📟), 🚫 🧵, 🚥 🔢 ✍ ⏮️ `async def` ⤴️ 👆 🔜 `await` 👈 🔢 🕐❔ 👆 🤙 ⚫️ 👆 📟. + +--- + +🔄, 👉 📶 📡 ℹ 👈 🔜 🎲 ⚠ 🚥 👆 👟 🔎 👫. + +⏪, 👆 🔜 👍 ⏮️ 📄 ⚪️➡️ 📄 🔛: 🏃 ❓. diff --git a/docs/em/docs/benchmarks.md b/docs/em/docs/benchmarks.md new file mode 100644 index 0000000000..003c3f62de --- /dev/null +++ b/docs/em/docs/benchmarks.md @@ -0,0 +1,34 @@ +# 📇 + +🔬 🇸🇲 📇 🎦 **FastAPI** 🈸 🏃‍♂ 🔽 Uvicorn 1️⃣ ⏩ 🐍 🛠️ 💪, 🕴 🔛 💃 & Uvicorn 👫 (⚙️ 🔘 FastAPI). (*) + +✋️ 🕐❔ ✅ 📇 & 🔺 👆 🔜 ✔️ 📄 🤯. + +## 📇 & 🚅 + +🕐❔ 👆 ✅ 📇, ⚫️ ⚠ 👀 📚 🧰 🎏 🆎 🔬 🌓. + +🎯, 👀 Uvicorn, 💃 & FastAPI 🔬 👯‍♂️ (👪 📚 🎏 🧰). + +🙅 ⚠ ❎ 🧰, 👍 🎭 ⚫️ 🔜 🤚. & 🏆 📇 🚫 💯 🌖 ⚒ 🚚 🧰. + +🔗 💖: + +* **Uvicorn**: 🔫 💽 + * **💃**: (⚙️ Uvicorn) 🕸 🕸 + * **FastAPI**: (⚙️ 💃) 🛠️ 🕸 ⏮️ 📚 🌖 ⚒ 🏗 🔗, ⏮️ 💽 🔬, ♒️. + +* **Uvicorn**: + * 🔜 ✔️ 🏆 🎭, ⚫️ 🚫 ✔️ 🌅 ➕ 📟 ↖️ ⚪️➡️ 💽 ⚫️. + * 👆 🚫🔜 ✍ 🈸 Uvicorn 🔗. 👈 🔜 ⛓ 👈 👆 📟 🔜 ✔️ 🔌 🌖 ⚖️ 🌘, 🌘, 🌐 📟 🚚 💃 (⚖️ **FastAPI**). & 🚥 👆 👈, 👆 🏁 🈸 🔜 ✔️ 🎏 🌥 ✔️ ⚙️ 🛠️ & 📉 👆 📱 📟 & 🐛. + * 🚥 👆 ⚖ Uvicorn, 🔬 ⚫️ 🛡 👸, Hypercorn, ✳, ♒️. 🈸 💽. +* **💃**: + * 🔜 ✔️ ⏭ 🏆 🎭, ⏮️ Uvicorn. 👐, 💃 ⚙️ Uvicorn 🏃. , ⚫️ 🎲 💪 🕴 🤚 "🐌" 🌘 Uvicorn ✔️ 🛠️ 🌅 📟. + * ✋️ ⚫️ 🚚 👆 🧰 🏗 🙅 🕸 🈸, ⏮️ 🕹 ⚓️ 🔛 ➡, ♒️. + * 🚥 👆 ⚖ 💃, 🔬 ⚫️ 🛡 🤣, 🏺, ✳, ♒️. 🕸 🛠️ (⚖️ 🕸). +* **FastAPI**: + * 🎏 🌌 👈 💃 ⚙️ Uvicorn & 🚫🔜 ⏩ 🌘 ⚫️, **FastAPI** ⚙️ 💃, ⚫️ 🚫🔜 ⏩ 🌘 ⚫️. + * FastAPI 🚚 🌅 ⚒ 🔛 🔝 💃. ⚒ 👈 👆 🌖 🕧 💪 🕐❔ 🏗 🔗, 💖 💽 🔬 & 🛠️. & ⚙️ ⚫️, 👆 🤚 🏧 🧾 🆓 (🏧 🧾 🚫 🚮 🌥 🏃‍♂ 🈸, ⚫️ 🏗 🔛 🕴). + * 🚥 👆 🚫 ⚙️ FastAPI & ⚙️ 💃 🔗 (⚖️ ➕1️⃣ 🧰, 💖 🤣, 🏺, 🆘, ♒️) 👆 🔜 ✔️ 🛠️ 🌐 💽 🔬 & 🛠️ 👆. , 👆 🏁 🈸 🔜 ✔️ 🎏 🌥 🚥 ⚫️ 🏗 ⚙️ FastAPI. & 📚 💼, 👉 💽 🔬 & 🛠️ 🦏 💸 📟 ✍ 🈸. + * , ⚙️ FastAPI 👆 ♻ 🛠️ 🕰, 🐛, ⏸ 📟, & 👆 🔜 🎲 🤚 🎏 🎭 (⚖️ 👍) 👆 🔜 🚥 👆 🚫 ⚙️ ⚫️ (👆 🔜 ✔️ 🛠️ ⚫️ 🌐 👆 📟). + * 🚥 👆 ⚖ FastAPI, 🔬 ⚫️ 🛡 🕸 🈸 🛠️ (⚖️ ⚒ 🧰) 👈 🚚 💽 🔬, 🛠️ & 🧾, 💖 🏺-apispec, NestJS, ♨, ♒️. 🛠️ ⏮️ 🛠️ 🏧 💽 🔬, 🛠️ & 🧾. diff --git a/docs/em/docs/contributing.md b/docs/em/docs/contributing.md new file mode 100644 index 0000000000..748928f88f --- /dev/null +++ b/docs/em/docs/contributing.md @@ -0,0 +1,465 @@ +# 🛠️ - 📉 + +🥇, 👆 💪 💚 👀 🔰 🌌 [ℹ FastAPI & 🤚 ℹ](help-fastapi.md){.internal-link target=_blank}. + +## 🛠️ + +🚥 👆 ⏪ 🖖 🗃 & 👆 💭 👈 👆 💪 ⏬ 🤿 📟, 📥 📄 ⚒ 🆙 👆 🌐. + +### 🕹 🌐 ⏮️ `venv` + +👆 💪 ✍ 🕹 🌐 📁 ⚙️ 🐍 `venv` 🕹: + +
+ +```console +$ python -m venv env +``` + +
+ +👈 🔜 ✍ 📁 `./env/` ⏮️ 🐍 💱 & ⤴️ 👆 🔜 💪 ❎ 📦 👈 ❎ 🌐. + +### 🔓 🌐 + +🔓 🆕 🌐 ⏮️: + +=== "💾, 🇸🇻" + +
+ + ```console + $ source ./env/bin/activate + ``` + +
+ +=== "🚪 📋" + +
+ + ```console + $ .\env\Scripts\Activate.ps1 + ``` + +
+ +=== "🚪 🎉" + + ⚖️ 🚥 👆 ⚙️ 🎉 🖥 (✅ 🐛 🎉): + +
+ + ```console + $ source ./env/Scripts/activate + ``` + +
+ +✅ ⚫️ 👷, ⚙️: + +=== "💾, 🇸🇻, 🚪 🎉" + +
+ + ```console + $ which pip + + some/directory/fastapi/env/bin/pip + ``` + +
+ +=== "🚪 📋" + +
+ + ```console + $ Get-Command pip + + some/directory/fastapi/env/bin/pip + ``` + +
+ +🚥 ⚫️ 🎦 `pip` 💱 `env/bin/pip` ⤴️ ⚫️ 👷. 👶 + +⚒ 💭 👆 ✔️ 📰 🐖 ⏬ 🔛 👆 🕹 🌐 ❎ ❌ 🔛 ⏭ 📶: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +!!! tip + 🔠 🕰 👆 ❎ 🆕 📦 ⏮️ `pip` 🔽 👈 🌐, 🔓 🌐 🔄. + + 👉 ⚒ 💭 👈 🚥 👆 ⚙️ 📶 📋 ❎ 👈 📦, 👆 ⚙️ 1️⃣ ⚪️➡️ 👆 🇧🇿 🌐 & 🚫 🙆 🎏 👈 💪 ❎ 🌐. + +### 🐖 + +⏮️ 🔓 🌐 🔬 🔛: + +
+ +```console +$ pip install -r requirements.txt + +---> 100% +``` + +
+ +⚫️ 🔜 ❎ 🌐 🔗 & 👆 🇧🇿 FastAPI 👆 🇧🇿 🌐. + +#### ⚙️ 👆 🇧🇿 FastAPI + +🚥 👆 ✍ 🐍 📁 👈 🗄 & ⚙️ FastAPI, & 🏃 ⚫️ ⏮️ 🐍 ⚪️➡️ 👆 🇧🇿 🌐, ⚫️ 🔜 ⚙️ 👆 🇧🇿 FastAPI ℹ 📟. + +& 🚥 👆 ℹ 👈 🇧🇿 FastAPI ℹ 📟, ⚫️ ❎ ⏮️ `-e`, 🕐❔ 👆 🏃 👈 🐍 📁 🔄, ⚫️ 🔜 ⚙️ 🍋 ⏬ FastAPI 👆 ✍. + +👈 🌌, 👆 🚫 ✔️ "❎" 👆 🇧🇿 ⏬ 💪 💯 🔠 🔀. + +### 📁 + +📤 ✍ 👈 👆 💪 🏃 👈 🔜 📁 & 🧹 🌐 👆 📟: + +
+ +```console +$ bash scripts/format.sh +``` + +
+ +⚫️ 🔜 🚘-😇 🌐 👆 🗄. + +⚫️ 😇 👫 ☑, 👆 💪 ✔️ FastAPI ❎ 🌐 👆 🌐, ⏮️ 📋 📄 🔛 ⚙️ `-e`. + +## 🩺 + +🥇, ⚒ 💭 👆 ⚒ 🆙 👆 🌐 🔬 🔛, 👈 🔜 ❎ 🌐 📄. + +🧾 ⚙️ . + +& 📤 ➕ 🧰/✍ 🥉 🍵 ✍ `./scripts/docs.py`. + +!!! tip + 👆 🚫 💪 👀 📟 `./scripts/docs.py`, 👆 ⚙️ ⚫️ 📋 ⏸. + +🌐 🧾 ✍ 📁 📁 `./docs/en/`. + +📚 🔰 ✔️ 🍫 📟. + +🌅 💼, 👫 🍫 📟 ☑ 🏁 🈸 👈 💪 🏃. + +👐, 👈 🍫 📟 🚫 ✍ 🔘 ✍, 👫 🐍 📁 `./docs_src/` 📁. + +& 👈 🐍 📁 🔌/💉 🧾 🕐❔ 🏭 🕸. + +### 🩺 💯 + +🏆 💯 🤙 🏃 🛡 🖼 ℹ 📁 🧾. + +👉 ℹ ⚒ 💭 👈: + +* 🧾 🆙 📅. +* 🧾 🖼 💪 🏃. +* 🌅 ⚒ 📔 🧾, 🚚 💯 💰. + +⏮️ 🇧🇿 🛠️, 📤 ✍ 👈 🏗 🕸 & ✅ 🙆 🔀, 🖖-🔫: + +
+ +```console +$ python ./scripts/docs.py live + +[INFO] Serving on http://127.0.0.1:8008 +[INFO] Start watching changes +[INFO] Start detecting changes +``` + +
+ +⚫️ 🔜 🍦 🧾 🔛 `http://127.0.0.1:8008`. + +👈 🌌, 👆 💪 ✍ 🧾/ℹ 📁 & 👀 🔀 🖖. + +#### 🏎 ✳ (📦) + +👩‍🌾 📥 🎦 👆 ❔ ⚙️ ✍ `./scripts/docs.py` ⏮️ `python` 📋 🔗. + +✋️ 👆 💪 ⚙️ 🏎 ✳, & 👆 🔜 🤚 ✍ 👆 📶 📋 ⏮️ ❎ 🛠️. + +🚥 👆 ❎ 🏎 ✳, 👆 💪 ❎ 🛠️ ⏮️: + +
+ +```console +$ typer --install-completion + +zsh completion installed in /home/user/.bashrc. +Completion will take effect once you restart the terminal. +``` + +
+ +### 📱 & 🩺 🎏 🕰 + +🚥 👆 🏃 🖼 ⏮️, ✅: + +
+ +```console +$ uvicorn tutorial001:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Uvicorn 🔢 🔜 ⚙️ ⛴ `8000`, 🧾 🔛 ⛴ `8008` 🏆 🚫 ⚔. + +### ✍ + +ℹ ⏮️ ✍ 📶 🌅 👍 ❗ & ⚫️ 💪 🚫 🔨 🍵 ℹ ⚪️➡️ 👪. 👶 👶 + +📥 📶 ℹ ⏮️ ✍. + +#### 💁‍♂ & 📄 + +* ✅ ⏳ ♻ 🚲 📨 👆 🇪🇸 & 🚮 📄 ✔ 🔀 ⚖️ ✔ 👫. + +!!! tip + 👆 💪 🚮 🏤 ⏮️ 🔀 🔑 ♻ 🚲 📨. + + ✅ 🩺 🔃 ❎ 🚲 📨 📄 ✔ ⚫️ ⚖️ 📨 🔀. + +* ✅ 👀 🚥 📤 1️⃣ 🛠️ ✍ 👆 🇪🇸. + +* 🚮 👁 🚲 📨 📍 📃 💬. 👈 🔜 ⚒ ⚫️ 🌅 ⏩ 🎏 📄 ⚫️. + +🇪🇸 👤 🚫 💬, 👤 🔜 ⌛ 📚 🎏 📄 ✍ ⏭ 🔗. + +* 👆 💪 ✅ 🚥 📤 ✍ 👆 🇪🇸 & 🚮 📄 👫, 👈 🔜 ℹ 👤 💭 👈 ✍ ☑ & 👤 💪 🔗 ⚫️. + +* ⚙️ 🎏 🐍 🖼 & 🕴 💬 ✍ 🩺. 👆 🚫 ✔️ 🔀 🕳 👉 👷. + +* ⚙️ 🎏 🖼, 📁 📛, & 🔗. 👆 🚫 ✔️ 🔀 🕳 ⚫️ 👷. + +* ✅ 2️⃣-🔤 📟 🇪🇸 👆 💚 💬 👆 💪 ⚙️ 🏓 📇 💾 6️⃣3️⃣9️⃣-1️⃣ 📟. + +#### ♻ 🇪🇸 + +➡️ 💬 👆 💚 💬 📃 🇪🇸 👈 ⏪ ✔️ ✍ 📃, 💖 🇪🇸. + +💼 🇪🇸, 2️⃣-🔤 📟 `es`. , 📁 🇪🇸 ✍ 🔎 `docs/es/`. + +!!! tip + 👑 ("🛂") 🇪🇸 🇪🇸, 🔎 `docs/en/`. + +🔜 🏃 🖖 💽 🩺 🇪🇸: + +
+ +```console +// Use the command "live" and pass the language code as a CLI argument +$ python ./scripts/docs.py live es + +[INFO] Serving on http://127.0.0.1:8008 +[INFO] Start watching changes +[INFO] Start detecting changes +``` + +
+ +🔜 👆 💪 🚶 http://127.0.0.1:8008 & 👀 👆 🔀 🖖. + +🚥 👆 👀 FastAPI 🩺 🕸, 👆 🔜 👀 👈 🔠 🇪🇸 ✔️ 🌐 📃. ✋️ 📃 🚫 💬 & ✔️ 📨 🔃 ❌ ✍. + +✋️ 🕐❔ 👆 🏃 ⚫️ 🌐 💖 👉, 👆 🔜 🕴 👀 📃 👈 ⏪ 💬. + +🔜 ➡️ 💬 👈 👆 💚 🚮 ✍ 📄 [⚒](features.md){.internal-link target=_blank}. + +* 📁 📁: + +``` +docs/en/docs/features.md +``` + +* 📋 ⚫️ ⚫️❔ 🎏 🗺 ✋️ 🇪🇸 👆 💚 💬, ✅: + +``` +docs/es/docs/features.md +``` + +!!! tip + 👀 👈 🕴 🔀 ➡ & 📁 📛 🇪🇸 📟, ⚪️➡️ `en` `es`. + +* 🔜 📂 ⬜ 📁 📁 🇪🇸: + +``` +docs/en/mkdocs.yml +``` + +* 🔎 🥉 🌐❔ 👈 `docs/features.md` 🔎 📁 📁. 👱 💖: + +```YAML hl_lines="8" +site_name: FastAPI +# More stuff +nav: +- FastAPI: index.md +- Languages: + - en: / + - es: /es/ +- features.md +``` + +* 📂 ⬜ 📁 📁 🇪🇸 👆 ✍, ✅: + +``` +docs/es/mkdocs.yml +``` + +* 🚮 ⚫️ 📤 ☑ 🎏 🗺 ⚫️ 🇪🇸, ✅: + +```YAML hl_lines="8" +site_name: FastAPI +# More stuff +nav: +- FastAPI: index.md +- Languages: + - en: / + - es: /es/ +- features.md +``` + +⚒ 💭 👈 🚥 📤 🎏 ⛔, 🆕 ⛔ ⏮️ 👆 ✍ ⚫️❔ 🎏 ✔ 🇪🇸 ⏬. + +🚥 👆 🚶 👆 🖥 👆 🔜 👀 👈 🔜 🩺 🎦 👆 🆕 📄. 👶 + +🔜 👆 💪 💬 ⚫️ 🌐 & 👀 ❔ ⚫️ 👀 👆 🖊 📁. + +#### 🆕 🇪🇸 + +➡️ 💬 👈 👆 💚 🚮 ✍ 🇪🇸 👈 🚫 💬, 🚫 📃. + +➡️ 💬 👆 💚 🚮 ✍ 🇭🇹, & ⚫️ 🚫 📤 🩺. + +✅ 🔗 ⚪️➡️ 🔛, 📟 "🇭🇹" `ht`. + +⏭ 🔁 🏃 ✍ 🏗 🆕 ✍ 📁: + +
+ +```console +// Use the command new-lang, pass the language code as a CLI argument +$ python ./scripts/docs.py new-lang ht + +Successfully initialized: docs/ht +Updating ht +Updating en +``` + +
+ +🔜 👆 💪 ✅ 👆 📟 👨‍🎨 ⏳ ✍ 📁 `docs/ht/`. + +!!! tip + ✍ 🥇 🚲 📨 ⏮️ 👉, ⚒ 🆙 📳 🆕 🇪🇸, ⏭ ❎ ✍. + + 👈 🌌 🎏 💪 ℹ ⏮️ 🎏 📃 ⏪ 👆 👷 🔛 🥇 🕐. 👶 + +▶️ ✍ 👑 📃, `docs/ht/index.md`. + +⤴️ 👆 💪 😣 ⏮️ ⏮️ 👩‍🌾, "♻ 🇪🇸". + +##### 🆕 🇪🇸 🚫 🐕‍🦺 + +🚥 🕐❔ 🏃‍♂ 🖖 💽 ✍ 👆 🤚 ❌ 🔃 🇪🇸 🚫 ➖ 🐕‍🦺, 🕳 💖: + +``` + raise TemplateNotFound(template) +jinja2.exceptions.TemplateNotFound: partials/language/xx.html +``` + +👈 ⛓ 👈 🎢 🚫 🐕‍🦺 👈 🇪🇸 (👉 💼, ⏮️ ❌ 2️⃣-🔤 📟 `xx`). + +✋️ 🚫 😟, 👆 💪 ⚒ 🎢 🇪🇸 🇪🇸 & ⤴️ 💬 🎚 🩺. + +🚥 👆 💪 👈, ✍ `mkdocs.yml` 👆 🆕 🇪🇸, ⚫️ 🔜 ✔️ 🕳 💖: + +```YAML hl_lines="5" +site_name: FastAPI +# More stuff +theme: + # More stuff + language: xx +``` + +🔀 👈 🇪🇸 ⚪️➡️ `xx` (⚪️➡️ 👆 🇪🇸 📟) `en`. + +⤴️ 👆 💪 ▶️ 🖖 💽 🔄. + +#### 🎮 🏁 + +🕐❔ 👆 ⚙️ ✍ `./scripts/docs.py` ⏮️ `live` 📋 ⚫️ 🕴 🎦 📁 & ✍ 💪 ⏮️ 🇪🇸. + +✋️ 🕐 👆 🔨, 👆 💪 💯 ⚫️ 🌐 ⚫️ 🔜 👀 💳. + +👈, 🥇 🏗 🌐 🩺: + +
+ +```console +// Use the command "build-all", this will take a bit +$ python ./scripts/docs.py build-all + +Updating es +Updating en +Building docs for: en +Building docs for: es +Successfully built docs for: es +Copying en index.md to README.md +``` + +
+ +👈 🏗 🌐 🩺 `./docs_build/` 🔠 🇪🇸. 👉 🔌 ❎ 🙆 📁 ⏮️ ❌ ✍, ⏮️ 🗒 💬 👈 "👉 📁 🚫 ✔️ ✍". ✋️ 👆 🚫 ✔️ 🕳 ⏮️ 👈 📁. + +⤴️ ⚫️ 🏗 🌐 👈 🔬 ⬜ 🕸 🔠 🇪🇸, 🌀 👫, & 🏗 🏁 🔢 `./site/`. + +⤴️ 👆 💪 🍦 👈 ⏮️ 📋 `serve`: + +
+ +```console +// Use the command "serve" after running "build-all" +$ python ./scripts/docs.py serve + +Warning: this is a very simple server. For development, use mkdocs serve instead. +This is here only to preview a site with translations already built. +Make sure you run the build-all command first. +Serving at: http://127.0.0.1:8008 +``` + +
+ +## 💯 + +📤 ✍ 👈 👆 💪 🏃 🌐 💯 🌐 📟 & 🏗 💰 📄 🕸: + +
+ +```console +$ bash scripts/test-cov-html.sh +``` + +
+ +👉 📋 🏗 📁 `./htmlcov/`, 🚥 👆 📂 📁 `./htmlcov/index.html` 👆 🖥, 👆 💪 🔬 🖥 🇹🇼 📟 👈 📔 💯, & 👀 🚥 📤 🙆 🇹🇼 ❌. diff --git a/docs/em/docs/deployment/concepts.md b/docs/em/docs/deployment/concepts.md new file mode 100644 index 0000000000..8ce7754114 --- /dev/null +++ b/docs/em/docs/deployment/concepts.md @@ -0,0 +1,311 @@ +# 🛠️ 🔧 + +🕐❔ 🛠️ **FastAPI** 🈸, ⚖️ 🤙, 🙆 🆎 🕸 🛠️, 📤 📚 🔧 👈 👆 🎲 💅 🔃, & ⚙️ 👫 👆 💪 🔎 **🏆 ☑** 🌌 **🛠️ 👆 🈸**. + +⚠ 🔧: + +* 💂‍♂ - 🇺🇸🔍 +* 🏃‍♂ 🔛 🕴 +* ⏏ +* 🧬 (🔢 🛠️ 🏃) +* 💾 +* ⏮️ 🔁 ⏭ ▶️ + +👥 🔜 👀 ❔ 👫 🔜 📉 **🛠️**. + +🔚, 🏆 🎯 💪 **🍦 👆 🛠️ 👩‍💻** 🌌 👈 **🔐**, **❎ 📉**, & ⚙️ **📊 ℹ** (🖼 🛰 💽/🕹 🎰) ♻ 💪. 👶 + +👤 🔜 💬 👆 🍖 🌖 🔃 👫 **🔧** 📥, & 👈 🔜 🤞 🤝 👆 **🤔** 👆 🔜 💪 💭 ❔ 🛠️ 👆 🛠️ 📶 🎏 🌐, 🎲 **🔮** 🕐 👈 🚫 🔀. + +🤔 👫 🔧, 👆 🔜 💪 **🔬 & 🔧** 🏆 🌌 🛠️ **👆 👍 🔗**. + +⏭ 📃, 👤 🔜 🤝 👆 🌅 **🧱 🍮** 🛠️ FastAPI 🈸. + +✋️ 🔜, ➡️ ✅ 👉 ⚠ **⚛ 💭**. 👫 🔧 ✔ 🙆 🎏 🆎 🕸 🛠️. 👶 + +## 💂‍♂ - 🇺🇸🔍 + +[⏮️ 📃 🔃 🇺🇸🔍](./https.md){.internal-link target=_blank} 👥 🇭🇲 🔃 ❔ 🇺🇸🔍 🚚 🔐 👆 🛠️. + +👥 👀 👈 🇺🇸🔍 🛎 🚚 🦲 **🔢** 👆 🈸 💽, **🤝 ❎ 🗳**. + +& 📤 ✔️ 🕳 🈚 **♻ 🇺🇸🔍 📄**, ⚫️ 💪 🎏 🦲 ⚖️ ⚫️ 💪 🕳 🎏. + +### 🖼 🧰 🇺🇸🔍 + +🧰 👆 💪 ⚙️ 🤝 ❎ 🗳: + +* Traefik + * 🔁 🍵 📄 🔕 👶 +* 📥 + * 🔁 🍵 📄 🔕 👶 +* 👌 + * ⏮️ 🔢 🦲 💖 Certbot 📄 🔕 +* ✳ + * ⏮️ 🔢 🦲 💖 Certbot 📄 🔕 +* Kubernete ⏮️ 🚧 🕹 💖 👌 + * ⏮️ 🔢 🦲 💖 🛂-👨‍💼 📄 🔕 +* 🍵 🔘 ☁ 🐕‍🦺 🍕 👫 🐕‍🦺 (✍ 🔛 👶) + +➕1️⃣ 🎛 👈 👆 💪 ⚙️ **☁ 🐕‍🦺** 👈 🔨 🌖 👷 ✅ ⚒ 🆙 🇺🇸🔍. ⚫️ 💪 ✔️ 🚫 ⚖️ 🈚 👆 🌅, ♒️. ✋️ 👈 💼, 👆 🚫🔜 ✔️ ⚒ 🆙 🤝 ❎ 🗳 👆. + +👤 🔜 🎦 👆 🧱 🖼 ⏭ 📃. + +--- + +⤴️ ⏭ 🔧 🤔 🌐 🔃 📋 🏃 👆 ☑ 🛠️ (✅ Uvicorn). + +## 📋 & 🛠️ + +👥 🔜 💬 📚 🔃 🏃 "**🛠️**", ⚫️ ⚠ ✔️ ☯ 🔃 ⚫️❔ ⚫️ ⛓, & ⚫️❔ 🔺 ⏮️ 🔤 "**📋**". + +### ⚫️❔ 📋 + +🔤 **📋** 🛎 ⚙️ 🔬 📚 👜: + +* **📟** 👈 👆 ✍, **🐍 📁**. +* **📁** 👈 💪 **🛠️** 🏃‍♂ ⚙️, 🖼: `python`, `python.exe` ⚖️ `uvicorn`. +* 🎯 📋 ⏪ ⚫️ **🏃‍♂** 🔛 🏗 ⚙️, ⚙️ 💽, & ♻ 👜 🔛 💾. 👉 🤙 **🛠️**. + +### ⚫️❔ 🛠️ + +🔤 **🛠️** 🛎 ⚙️ 🌖 🎯 🌌, 🕴 🔗 👜 👈 🏃 🏃‍♂ ⚙️ (💖 🏁 ☝ 🔛): + +* 🎯 📋 ⏪ ⚫️ **🏃‍♂** 🔛 🏃‍♂ ⚙️. + * 👉 🚫 🔗 📁, 🚫 📟, ⚫️ 🔗 **🎯** 👜 👈 ➖ **🛠️** & 🔄 🏃‍♂ ⚙️. +* 🙆 📋, 🙆 📟, **💪 🕴 👜** 🕐❔ ⚫️ ➖ **🛠️**. , 🕐❔ 📤 **🛠️ 🏃**. +* 🛠️ 💪 **❎** (⚖️ "💥") 👆, ⚖️ 🏃‍♂ ⚙️. 👈 ☝, ⚫️ ⛔️ 🏃/➖ 🛠️, & ⚫️ 💪 **🙅‍♂ 📏 👜**. +* 🔠 🈸 👈 👆 ✔️ 🏃 🔛 👆 💻 ✔️ 🛠️ ⛅ ⚫️, 🔠 🏃‍♂ 📋, 🔠 🚪, ♒️. & 📤 🛎 📚 🛠️ 🏃 **🎏 🕰** ⏪ 💻 🔛. +* 📤 💪 **💗 🛠️** **🎏 📋** 🏃 🎏 🕰. + +🚥 👆 ✅ 👅 "📋 👨‍💼" ⚖️ "⚙️ 🖥" (⚖️ 🎏 🧰) 👆 🏃‍♂ ⚙️, 👆 🔜 💪 👀 📚 👈 🛠️ 🏃‍♂. + +& , 🖼, 👆 🔜 🎲 👀 👈 📤 💗 🛠️ 🏃 🎏 🖥 📋 (🦎, 💄, 📐, ♒️). 👫 🛎 🏃 1️⃣ 🛠️ 📍 📑, ➕ 🎏 ➕ 🛠️. + + + +--- + +🔜 👈 👥 💭 🔺 🖖 ⚖ **🛠️** & **📋**, ➡️ 😣 💬 🔃 🛠️. + +## 🏃‍♂ 🔛 🕴 + +🌅 💼, 🕐❔ 👆 ✍ 🕸 🛠️, 👆 💚 ⚫️ **🕧 🏃‍♂**, ➡, 👈 👆 👩‍💻 💪 🕧 🔐 ⚫️. 👉 ↗️, 🚥 👆 ✔️ 🎯 🤔 ⚫️❔ 👆 💚 ⚫️ 🏃 🕴 🎯 ⚠, ✋️ 🌅 🕰 👆 💚 ⚫️ 🕧 🏃‍♂ & **💪**. + +### 🛰 💽 + +🕐❔ 👆 ⚒ 🆙 🛰 💽 (☁ 💽, 🕹 🎰, ♒️.) 🙅 👜 👆 💪 🏃 Uvicorn (⚖️ 🎏) ❎, 🎏 🌌 👆 🕐❔ 🛠️ 🌐. + +& ⚫️ 🔜 👷 & 🔜 ⚠ **⏮️ 🛠️**. + +✋️ 🚥 👆 🔗 💽 💸, **🏃‍♂ 🛠️** 🔜 🎲 ☠️. + +& 🚥 💽 ⏏ (🖼 ⏮️ ℹ, ⚖️ 🛠️ ⚪️➡️ ☁ 🐕‍🦺) 👆 🎲 **🏆 🚫 👀 ⚫️**. & ↩️ 👈, 👆 🏆 🚫 💭 👈 👆 ✔️ ⏏ 🛠️ ❎. , 👆 🛠️ 🔜 🚧 ☠️. 👶 + +### 🏃 🔁 🔛 🕴 + +🏢, 👆 🔜 🎲 💚 💽 📋 (✅ Uvicorn) ▶️ 🔁 🔛 💽 🕴, & 🍵 💪 🙆 **🗿 🏥**, ✔️ 🛠️ 🕧 🏃 ⏮️ 👆 🛠️ (✅ Uvicorn 🏃‍♂ 👆 FastAPI 📱). + +### 🎏 📋 + +🏆 👉, 👆 🔜 🛎 ✔️ **🎏 📋** 👈 🔜 ⚒ 💭 👆 🈸 🏃 🔛 🕴. & 📚 💼, ⚫️ 🔜 ⚒ 💭 🎏 🦲 ⚖️ 🈸 🏃, 🖼, 💽. + +### 🖼 🧰 🏃 🕴 + +🖼 🧰 👈 💪 👉 👨‍🏭: + +* ☁ +* Kubernete +* ☁ ✍ +* ☁ 🐝 📳 +* ✳ +* 👨‍💻 +* 🍵 🔘 ☁ 🐕‍🦺 🍕 👫 🐕‍🦺 +* 🎏... + +👤 🔜 🤝 👆 🌅 🧱 🖼 ⏭ 📃. + +## ⏏ + +🎏 ⚒ 💭 👆 🈸 🏃 🔛 🕴, 👆 🎲 💚 ⚒ 💭 ⚫️ **⏏** ⏮️ ❌. + +### 👥 ⚒ ❌ + +👥, 🗿, ⚒ **❌**, 🌐 🕰. 🖥 🌖 *🕧* ✔️ **🐛** 🕵‍♂ 🎏 🥉. 👶 + +& 👥 👩‍💻 🚧 📉 📟 👥 🔎 👈 🐛 & 👥 🛠️ 🆕 ⚒ (🎲 ❎ 🆕 🐛 💁‍♂️ 👶). + +### 🤪 ❌ 🔁 🍵 + +🕐❔ 🏗 🕸 🔗 ⏮️ FastAPI, 🚥 📤 ❌ 👆 📟, FastAPI 🔜 🛎 🔌 ⚫️ 👁 📨 👈 ⏲ ❌. 🛡 + +👩‍💻 🔜 🤚 **5️⃣0️⃣0️⃣ 🔗 💽 ❌** 👈 📨, ✋️ 🈸 🔜 😣 👷 ⏭ 📨 ↩️ 💥 🍕. + +### 🦏 ❌ - 💥 + +👐, 📤 5️⃣📆 💼 🌐❔ 👥 ✍ 📟 👈 **💥 🎂 🈸** ⚒ Uvicorn & 🐍 💥. 👶 + +& , 👆 🔜 🎲 🚫 💚 🈸 🚧 ☠️ ↩️ 📤 ❌ 1️⃣ 🥉, 👆 🎲 💚 ⚫️ **😣 🏃** 🌘 *➡ 🛠️* 👈 🚫 💔. + +### ⏏ ⏮️ 💥 + +✋️ 👈 💼 ⏮️ 🤙 👎 ❌ 👈 💥 🏃‍♂ **🛠️**, 👆 🔜 💚 🔢 🦲 👈 🈚 **🔁** 🛠️, 🌘 👩‍❤‍👨 🕰... + +!!! tip + ...👐 🚥 🎂 🈸 **💥 ⏪** ⚫️ 🎲 🚫 ⚒ 🔑 🚧 🔁 ⚫️ ♾. ✋️ 📚 💼, 👆 🔜 🎲 👀 ⚫️ ⏮️ 🛠️, ⚖️ 🌘 ▶️️ ⏮️ 🛠️. + + ➡️ 🎯 🔛 👑 💼, 🌐❔ ⚫️ 💪 💥 🍕 🎯 💼 **🔮**, & ⚫️ ⚒ 🔑 ⏏ ⚫️. + +👆 🔜 🎲 💚 ✔️ 👜 🈚 🔁 👆 🈸 **🔢 🦲**, ↩️ 👈 ☝, 🎏 🈸 ⏮️ Uvicorn & 🐍 ⏪ 💥, 📤 🕳 🎏 📟 🎏 📱 👈 💪 🕳 🔃 ⚫️. + +### 🖼 🧰 ⏏ 🔁 + +🏆 💼, 🎏 🧰 👈 ⚙️ **🏃 📋 🔛 🕴** ⚙️ 🍵 🏧 **⏏**. + +🖼, 👉 💪 🍵: + +* ☁ +* Kubernete +* ☁ ✍ +* ☁ 🐝 📳 +* ✳ +* 👨‍💻 +* 🍵 🔘 ☁ 🐕‍🦺 🍕 👫 🐕‍🦺 +* 🎏... + +## 🧬 - 🛠️ & 💾 + +⏮️ FastAPI 🈸, ⚙️ 💽 📋 💖 Uvicorn, 🏃‍♂ ⚫️ 🕐 **1️⃣ 🛠️** 💪 🍦 💗 👩‍💻 🔁. + +✋️ 📚 💼, 👆 🔜 💚 🏃 📚 👨‍🏭 🛠️ 🎏 🕰. + +### 💗 🛠️ - 👨‍🏭 + +🚥 👆 ✔️ 🌅 👩‍💻 🌘 ⚫️❔ 👁 🛠️ 💪 🍵 (🖼 🚥 🕹 🎰 🚫 💁‍♂️ 🦏) & 👆 ✔️ **💗 🐚** 💽 💽, ⤴️ 👆 💪 ✔️ **💗 🛠️** 🏃‍♂ ⏮️ 🎏 🈸 🎏 🕰, & 📎 🌐 📨 👪 👫. + +🕐❔ 👆 🏃 **💗 🛠️** 🎏 🛠️ 📋, 👫 🛎 🤙 **👨‍🏭**. + +### 👨‍🏭 🛠️ & ⛴ + +💭 ⚪️➡️ 🩺 [🔃 🇺🇸🔍](./https.md){.internal-link target=_blank} 👈 🕴 1️⃣ 🛠️ 💪 👂 🔛 1️⃣ 🌀 ⛴ & 📢 📢 💽 ❓ + +👉 ☑. + +, 💪 ✔️ **💗 🛠️** 🎏 🕰, 📤 ✔️ **👁 🛠️ 👂 🔛 ⛴** 👈 ⤴️ 📶 📻 🔠 👨‍🏭 🛠️ 🌌. + +### 💾 📍 🛠️ + +🔜, 🕐❔ 📋 📐 👜 💾, 🖼, 🎰 🏫 🏷 🔢, ⚖️ 🎚 ⭕ 📁 🔢, 🌐 👈 **🍴 👄 💾 (💾)** 💽. + +& 💗 🛠️ 🛎 **🚫 💰 🙆 💾**. 👉 ⛓ 👈 🔠 🏃 🛠️ ✔️ 🚮 👍 👜, 🔢, & 💾. & 🚥 👆 😩 ⭕ 💸 💾 👆 📟, **🔠 🛠️** 🔜 🍴 🌓 💸 💾. + +### 💽 💾 + +🖼, 🚥 👆 📟 📐 🎰 🏫 🏷 ⏮️ **1️⃣ 💾 📐**, 🕐❔ 👆 🏃 1️⃣ 🛠️ ⏮️ 👆 🛠️, ⚫️ 🔜 🍴 🌘 1️⃣ 💾 💾. & 🚥 👆 ▶️ **4️⃣ 🛠️** (4️⃣ 👨‍🏭), 🔠 🔜 🍴 1️⃣ 💾 💾. 🌐, 👆 🛠️ 🔜 🍴 **4️⃣ 💾 💾**. + +& 🚥 👆 🛰 💽 ⚖️ 🕹 🎰 🕴 ✔️ 3️⃣ 💾 💾, 🔄 📐 🌅 🌘 4️⃣ 💾 💾 🔜 🤕 ⚠. 👶 + +### 💗 🛠️ - 🖼 + +👉 🖼, 📤 **👨‍💼 🛠️** 👈 ▶️ & 🎛 2️⃣ **👨‍🏭 🛠️**. + +👉 👨‍💼 🛠️ 🔜 🎲 1️⃣ 👂 🔛 **⛴** 📢. & ⚫️ 🔜 📶 🌐 📻 👨‍🏭 🛠️. + +👈 👨‍🏭 🛠️ 🔜 🕐 🏃‍♂ 👆 🈸, 👫 🔜 🎭 👑 📊 📨 **📨** & 📨 **📨**, & 👫 🔜 📐 🕳 👆 🚮 🔢 💾. + + + +& ↗️, 🎏 🎰 🔜 🎲 ✔️ **🎏 🛠️** 🏃 👍, ↖️ ⚪️➡️ 👆 🈸. + +😌 ℹ 👈 🌐 **💽 ⚙️** 🔠 🛠️ 💪 **🪀** 📚 🤭 🕰, ✋️ **💾 (💾)** 🛎 🚧 🌖 ⚖️ 🌘 **⚖**. + +🚥 👆 ✔️ 🛠️ 👈 🔨 ⭐ 💸 📊 🔠 🕰 & 👆 ✔️ 📚 👩‍💻, ⤴️ **💽 🛠️** 🔜 🎲 *⚖* (↩️ 🕧 🔜 🆙 & 🔽 🔜). + +### 🖼 🧬 🧰 & 🎛 + +📤 💪 📚 🎯 🏆 👉, & 👤 🔜 💬 👆 🌅 🔃 🎯 🎛 ⏭ 📃, 🖼 🕐❔ 💬 🔃 ☁ & 📦. + +👑 ⚛ 🤔 👈 📤 ✔️ **👁** 🦲 🚚 **⛴** **📢 📢**. & ⤴️ ⚫️ ✔️ ✔️ 🌌 **📶** 📻 🔁 **🛠️/👨‍🏭**. + +📥 💪 🌀 & 🎛: + +* **🐁** 🛠️ **Uvicorn 👨‍🏭** + * 🐁 🔜 **🛠️ 👨‍💼** 👂 🔛 **📢** & **⛴**, 🧬 🔜 ✔️ **💗 Uvicorn 👨‍🏭 🛠️** +* **Uvicorn** 🛠️ **Uvicorn 👨‍🏭** + * 1️⃣ Uvicorn **🛠️ 👨‍💼** 🔜 👂 🔛 **📢** & **⛴**, & ⚫️ 🔜 ▶️ **💗 Uvicorn 👨‍🏭 🛠️** +* **Kubernete** & 🎏 📎 **📦 ⚙️** + * 🕳 **☁** 🧽 🔜 👂 🔛 **📢** & **⛴**. 🧬 🔜 ✔️ **💗 📦**, 🔠 ⏮️ **1️⃣ Uvicorn 🛠️** 🏃‍♂ +* **☁ 🐕‍🦺** 👈 🍵 👉 👆 + * ☁ 🐕‍🦺 🔜 🎲 **🍵 🧬 👆**. ⚫️ 🔜 🎲 ➡️ 👆 🔬 **🛠️ 🏃**, ⚖️ **📦 🖼** ⚙️, 🙆 💼, ⚫️ 🔜 🌅 🎲 **👁 Uvicorn 🛠️**, & ☁ 🐕‍🦺 🔜 🈚 🔁 ⚫️. + +!!! tip + 🚫 😟 🚥 👫 🏬 🔃 **📦**, ☁, ⚖️ Kubernete 🚫 ⚒ 📚 🔑. + + 👤 🔜 💬 👆 🌅 🔃 📦 🖼, ☁, Kubernete, ♒️. 🔮 📃: [FastAPI 📦 - ☁](./docker.md){.internal-link target=_blank}. + +## ⏮️ 🔁 ⏭ ▶️ + +📤 📚 💼 🌐❔ 👆 💚 🎭 📶 **⏭ ▶️** 👆 🈸. + +🖼, 👆 💪 💚 🏃 **💽 🛠️**. + +✋️ 🌅 💼, 👆 🔜 💚 🎭 👉 🔁 🕴 **🕐**. + +, 👆 🔜 💚 ✔️ **👁 🛠️** 🎭 👈 **⏮️ 🔁**, ⏭ ▶️ 🈸. + +& 👆 🔜 ✔️ ⚒ 💭 👈 ⚫️ 👁 🛠️ 🏃 👈 ⏮️ 🔁 ** 🚥 ⏮️, 👆 ▶️ **💗 🛠️** (💗 👨‍🏭) 🈸 ⚫️. 🚥 👈 🔁 🏃 **💗 🛠️**, 👫 🔜 **❎** 👷 🏃‍♂ ⚫️ 🔛 **🔗**, & 🚥 📶 🕳 💎 💖 💽 🛠️, 👫 💪 🤕 ⚔ ⏮️ 🔠 🎏. + +↗️, 📤 💼 🌐❔ 📤 🙅‍♂ ⚠ 🏃 ⏮️ 🔁 💗 🕰, 👈 💼, ⚫️ 📚 ⏩ 🍵. + +!!! tip + , ✔️ 🤯 👈 ⚓️ 🔛 👆 🖥, 💼 👆 **5️⃣📆 🚫 💪 🙆 ⏮️ 🔁** ⏭ ▶️ 👆 🈸. + + 👈 💼, 👆 🚫🔜 ✔️ 😟 🔃 🙆 👉. 🤷 + +### 🖼 ⏮️ 🔁 🎛 + +👉 🔜 **🪀 🙇** 🔛 🌌 👆 **🛠️ 👆 ⚙️**, & ⚫️ 🔜 🎲 🔗 🌌 👆 ▶️ 📋, 🚚 ⏏, ♒️. + +📥 💪 💭: + +* "🕑 📦" Kubernete 👈 🏃 ⏭ 👆 📱 📦 +* 🎉 ✍ 👈 🏃 ⏮️ 🔁 & ⤴️ ▶️ 👆 🈸 + * 👆 🔜 💪 🌌 ▶️/⏏ *👈* 🎉 ✍, 🔍 ❌, ♒️. + +!!! tip + 👤 🔜 🤝 👆 🌅 🧱 🖼 🔨 👉 ⏮️ 📦 🔮 📃: [FastAPI 📦 - ☁](./docker.md){.internal-link target=_blank}. + +## ℹ 🛠️ + +👆 💽(Ⓜ) () **ℹ**, 👆 💪 🍴 ⚖️ **⚙️**, ⏮️ 👆 📋, 📊 🕰 🔛 💽, & 💾 💾 💪. + +❔ 🌅 ⚙️ ℹ 👆 💚 😩/♻ ❓ ⚫️ 💪 ⏩ 💭 "🚫 🌅", ✋️ 🌌, 👆 🔜 🎲 💚 🍴 **🌅 💪 🍵 💥**. + +🚥 👆 💸 3️⃣ 💽 ✋️ 👆 ⚙️ 🕴 🐥 🍖 👫 💾 & 💽, 👆 🎲 **🗑 💸** 👶, & 🎲 **🗑 💽 🔦 🏋️** 👶, ♒️. + +👈 💼, ⚫️ 💪 👻 ✔️ 🕴 2️⃣ 💽 & ⚙️ ↕ 🌐 👫 ℹ (💽, 💾, 💾, 🕸 💿, ♒️). + +🔛 🎏 ✋, 🚥 👆 ✔️ 2️⃣ 💽 & 👆 ⚙️ **1️⃣0️⃣0️⃣ 💯 👫 💽 & 💾**, ☝ 1️⃣ 🛠️ 🔜 💭 🌅 💾, & 💽 🔜 ✔️ ⚙️ 💾 "💾" (❔ 💪 💯 🕰 🐌), ⚖️ **💥**. ⚖️ 1️⃣ 🛠️ 💪 💪 📊 & 🔜 ✔️ ⌛ ⏭ 💽 🆓 🔄. + +👉 💼, ⚫️ 🔜 👍 🤚 **1️⃣ ➕ 💽** & 🏃 🛠️ 🔛 ⚫️ 👈 👫 🌐 ✔️ **🥃 💾 & 💽 🕰**. + +📤 🤞 👈 🤔 👆 ✔️ **🌵** ⚙️ 👆 🛠️. 🎲 ⚫️ 🚶 🦠, ⚖️ 🎲 🎏 🐕‍🦺 ⚖️ 🤖 ▶️ ⚙️ ⚫️. & 👆 💪 💚 ✔️ ➕ ℹ 🔒 👈 💼. + +👆 💪 🚮 **❌ 🔢** 🎯, 🖼, 🕳 **🖖 5️⃣0️⃣ 💯 9️⃣0️⃣ 💯** ℹ 🛠️. ☝ 👈 📚 🎲 👑 👜 👆 🔜 💚 ⚖ & ⚙️ ⚒ 👆 🛠️. + +👆 💪 ⚙️ 🙅 🧰 💖 `htop` 👀 💽 & 💾 ⚙️ 👆 💽 ⚖️ 💸 ⚙️ 🔠 🛠️. ⚖️ 👆 💪 ⚙️ 🌖 🏗 ⚖ 🧰, ❔ 5️⃣📆 📎 🤭 💽, ♒️. + +## 🌃 + +👆 ✔️ 👂 📥 👑 🔧 👈 👆 🔜 🎲 💪 ✔️ 🤯 🕐❔ 🤔 ❔ 🛠️ 👆 🈸: + +* 💂‍♂ - 🇺🇸🔍 +* 🏃‍♂ 🔛 🕴 +* ⏏ +* 🧬 (🔢 🛠️ 🏃) +* 💾 +* ⏮️ 🔁 ⏭ ▶️ + +🤔 👉 💭 & ❔ ✔ 👫 🔜 🤝 👆 🤔 💪 ✊ 🙆 🚫 🕐❔ 🛠️ & 🛠️ 👆 🛠️. 👶 + +⏭ 📄, 👤 🔜 🤝 👆 🌅 🧱 🖼 💪 🎛 👆 💪 ⏩. 👶 diff --git a/docs/em/docs/deployment/deta.md b/docs/em/docs/deployment/deta.md new file mode 100644 index 0000000000..89b6c4bdbe --- /dev/null +++ b/docs/em/docs/deployment/deta.md @@ -0,0 +1,258 @@ +# 🛠️ FastAPI 🔛 🪔 + +👉 📄 👆 🔜 💡 ❔ 💪 🛠️ **FastAPI** 🈸 🔛 🪔 ⚙️ 🆓 📄. 👶 + +⚫️ 🔜 ✊ 👆 🔃 **1️⃣0️⃣ ⏲**. + +!!! info + 🪔 **FastAPI** 💰. 👶 + +## 🔰 **FastAPI** 📱 + +* ✍ 📁 👆 📱, 🖼, `./fastapideta/` & ⛔ 🔘 ⚫️. + +### FastAPI 📟 + +* ✍ `main.py` 📁 ⏮️: + +```Python +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int): + return {"item_id": item_id} +``` + +### 📄 + +🔜, 🎏 📁 ✍ 📁 `requirements.txt` ⏮️: + +```text +fastapi +``` + +!!! tip + 👆 🚫 💪 ❎ Uvicorn 🛠️ 🔛 🪔, 👐 👆 🔜 🎲 💚 ❎ ⚫️ 🌐 💯 👆 📱. + +### 📁 📊 + +👆 🔜 🔜 ✔️ 1️⃣ 📁 `./fastapideta/` ⏮️ 2️⃣ 📁: + +``` +. +└── main.py +└── requirements.txt +``` + +## ✍ 🆓 🪔 🏧 + +🔜 ✍ 🆓 🏧 🔛 🪔, 👆 💪 📧 & 🔐. + +👆 🚫 💪 💳. + +## ❎ ✳ + +🕐 👆 ✔️ 👆 🏧, ❎ 🪔 : + +=== "💾, 🇸🇻" + +
+ + ```console + $ curl -fsSL https://get.deta.dev/cli.sh | sh + ``` + +
+ +=== "🚪 📋" + +
+ + ```console + $ iwr https://get.deta.dev/cli.ps1 -useb | iex + ``` + +
+ +⏮️ ❎ ⚫️, 📂 🆕 📶 👈 ❎ ✳ 🔍. + +🆕 📶, ✔ 👈 ⚫️ ☑ ❎ ⏮️: + +
+ +```console +$ deta --help + +Deta command line interface for managing deta micros. +Complete documentation available at https://docs.deta.sh + +Usage: + deta [flags] + deta [command] + +Available Commands: + auth Change auth settings for a deta micro + +... +``` + +
+ +!!! tip + 🚥 👆 ✔️ ⚠ ❎ ✳, ✅ 🛂 🪔 🩺. + +## 💳 ⏮️ ✳ + +🔜 💳 🪔 ⚪️➡️ ✳ ⏮️: + +
+ +```console +$ deta login + +Please, log in from the web page. Waiting.. +Logged in successfully. +``` + +
+ +👉 🔜 📂 🕸 🖥 & 🔓 🔁. + +## 🛠️ ⏮️ 🪔 + +⏭, 🛠️ 👆 🈸 ⏮️ 🪔 ✳: + +
+ +```console +$ deta new + +Successfully created a new micro + +// Notice the "endpoint" 🔍 + +{ + "name": "fastapideta", + "runtime": "python3.7", + "endpoint": "https://qltnci.deta.dev", + "visor": "enabled", + "http_auth": "enabled" +} + +Adding dependencies... + + +---> 100% + + +Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 +``` + +
+ +👆 🔜 👀 🎻 📧 🎏: + +```JSON hl_lines="4" +{ + "name": "fastapideta", + "runtime": "python3.7", + "endpoint": "https://qltnci.deta.dev", + "visor": "enabled", + "http_auth": "enabled" +} +``` + +!!! tip + 👆 🛠️ 🔜 ✔️ 🎏 `"endpoint"` 📛. + +## ✅ ⚫️ + +🔜 📂 👆 🖥 👆 `endpoint` 📛. 🖼 🔛 ⚫️ `https://qltnci.deta.dev`, ✋️ 👆 🔜 🎏. + +👆 🔜 👀 🎻 📨 ⚪️➡️ 👆 FastAPI 📱: + +```JSON +{ + "Hello": "World" +} +``` + +& 🔜 🚶 `/docs` 👆 🛠️, 🖼 🔛 ⚫️ 🔜 `https://qltnci.deta.dev/docs`. + +⚫️ 🔜 🎦 👆 🩺 💖: + + + +## 🛠️ 📢 🔐 + +🔢, 🪔 🔜 🍵 🤝 ⚙️ 🍪 👆 🏧. + +✋️ 🕐 👆 🔜, 👆 💪 ⚒ ⚫️ 📢 ⏮️: + +
+ +```console +$ deta auth disable + +Successfully disabled http auth +``` + +
+ +🔜 👆 💪 💰 👈 📛 ⏮️ 🙆 & 👫 🔜 💪 🔐 👆 🛠️. 👶 + +## 🇺🇸🔍 + +㊗ ❗ 👆 🛠️ 👆 FastAPI 📱 🪔 ❗ 👶 👶 + +, 👀 👈 🪔 ☑ 🍵 🇺🇸🔍 👆, 👆 🚫 ✔️ ✊ 💅 👈 & 💪 💭 👈 👆 👩‍💻 🔜 ✔️ 🔐 🗜 🔗. 👶 👶 + +## ✅ 🕶 + +⚪️➡️ 👆 🩺 🎚 (👫 🔜 📛 💖 `https://qltnci.deta.dev/docs`) 📨 📨 👆 *➡ 🛠️* `/items/{item_id}`. + +🖼 ⏮️ 🆔 `5`. + +🔜 🚶 https://web.deta.sh. + +👆 🔜 👀 📤 📄 ◀️ 🤙 "◾" ⏮️ 🔠 👆 📱. + +👆 🔜 👀 📑 ⏮️ "ℹ", & 📑 "🕶", 🚶 📑 "🕶". + +📤 👆 💪 ✔ ⏮️ 📨 📨 👆 📱. + +👆 💪 ✍ 👫 & 🏤-🤾 👫. + + + +## 💡 🌅 + +☝, 👆 🔜 🎲 💚 🏪 💽 👆 📱 🌌 👈 😣 🔘 🕰. 👈 👆 💪 ⚙️ 🪔 🧢, ⚫️ ✔️ 👍 **🆓 🎚**. + +👆 💪 ✍ 🌅 🪔 🩺. + +## 🛠️ 🔧 + +👟 🔙 🔧 👥 🔬 [🛠️ 🔧](./concepts.md){.internal-link target=_blank}, 📥 ❔ 🔠 👫 🔜 🍵 ⏮️ 🪔: + +* **🇺🇸🔍**: 🍵 🪔, 👫 🔜 🤝 👆 📁 & 🍵 🇺🇸🔍 🔁. +* **🏃‍♂ 🔛 🕴**: 🍵 🪔, 🍕 👫 🐕‍🦺. +* **⏏**: 🍵 🪔, 🍕 👫 🐕‍🦺. +* **🧬**: 🍵 🪔, 🍕 👫 🐕‍🦺. +* **💾**: 📉 🔁 🪔, 👆 💪 📧 👫 📈 ⚫️. +* **⏮️ 🔁 ⏭ ▶️**: 🚫 🔗 🐕‍🦺, 👆 💪 ⚒ ⚫️ 👷 ⏮️ 👫 💾 ⚙️ ⚖️ 🌖 ✍. + +!!! note + 🪔 🔧 ⚒ ⚫️ ⏩ (& 🆓) 🛠️ 🙅 🈸 🔜. + + ⚫️ 💪 📉 📚 ⚙️ 💼, ✋️ 🎏 🕰, ⚫️ 🚫 🐕‍🦺 🎏, 💖 ⚙️ 🔢 💽 (↖️ ⚪️➡️ 🪔 👍 ☁ 💽 ⚙️), 🛃 🕹 🎰, ♒️. + + 👆 💪 ✍ 🌅 ℹ 🪔 🩺 👀 🚥 ⚫️ ▶️️ ⚒ 👆. diff --git a/docs/em/docs/deployment/docker.md b/docs/em/docs/deployment/docker.md new file mode 100644 index 0000000000..51ece5599e --- /dev/null +++ b/docs/em/docs/deployment/docker.md @@ -0,0 +1,698 @@ +# FastAPI 📦 - ☁ + +🕐❔ 🛠️ FastAPI 🈸 ⚠ 🎯 🏗 **💾 📦 🖼**. ⚫️ 🛎 🔨 ⚙️ **☁**. 👆 💪 ⤴️ 🛠️ 👈 📦 🖼 1️⃣ 👩‍❤‍👨 💪 🌌. + +⚙️ 💾 📦 ✔️ 📚 📈 ✅ **💂‍♂**, **🔬**, **🦁**, & 🎏. + +!!! tip + 🏃 & ⏪ 💭 👉 💩 ❓ 🦘 [`Dockerfile` 🔛 👶](#build-a-docker-image-for-fastapi). + +
+📁 🎮 👶 + +```Dockerfile +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] + +# If running behind a proxy like Nginx or Traefik add --proxy-headers +# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] +``` + +
+ +## ⚫️❔ 📦 + +📦 (✴️ 💾 📦) 📶 **💿** 🌌 📦 🈸 ✅ 🌐 👫 🔗 & 💪 📁 ⏪ 🚧 👫 ❎ ⚪️➡️ 🎏 📦 (🎏 🈸 ⚖️ 🦲) 🎏 ⚙️. + +💾 📦 🏃 ⚙️ 🎏 💾 💾 🦠 (🎰, 🕹 🎰, ☁ 💽, ♒️). 👉 ⛓ 👈 👫 📶 💿 (🔬 🌕 🕹 🎰 👍 🎂 🏃‍♂ ⚙️). + +👉 🌌, 📦 🍴 **🐥 ℹ**, 💸 ⭐ 🏃‍♂ 🛠️ 🔗 (🕹 🎰 🔜 🍴 🌅 🌅). + +📦 ✔️ 👫 👍 **❎** 🏃‍♂ 🛠️ (🛎 1️⃣ 🛠️), 📁 ⚙️, & 🕸, 🔬 🛠️, 💂‍♂, 🛠️, ♒️. + +## ⚫️❔ 📦 🖼 + +**📦** 🏃 ⚪️➡️ **📦 🖼**. + +📦 🖼 **🎻** ⏬ 🌐 📁, 🌐 🔢, & 🔢 📋/📋 👈 🔜 🎁 📦. **🎻** 📥 ⛓ 👈 📦 **🖼** 🚫 🏃, ⚫️ 🚫 ➖ 🛠️, ⚫️ 🕴 📦 📁 & 🗃. + +🔅 "**📦 🖼**" 👈 🏪 🎻 🎚,"**📦**" 🛎 🔗 🏃‍♂ 👐, 👜 👈 ➖ **🛠️**. + +🕐❔ **📦** ▶️ & 🏃‍♂ (▶️ ⚪️➡️ **📦 🖼**) ⚫️ 💪 ✍ ⚖️ 🔀 📁, 🌐 🔢, ♒️. 👈 🔀 🔜 🔀 🕴 👈 📦, ✋️ 🔜 🚫 😣 👽 📦 🖼 (🔜 🚫 🖊 💾). + +📦 🖼 ⭐ **📋** 📁 & 🎚, ✅ `python` & 📁 `main.py`. + +& **📦** ⚫️ (🔅 **📦 🖼**) ☑ 🏃 👐 🖼, ⭐ **🛠️**. 👐, 📦 🏃 🕴 🕐❔ ⚫️ ✔️ **🛠️ 🏃** (& 🛎 ⚫️ 🕴 👁 🛠️). 📦 ⛔️ 🕐❔ 📤 🙅‍♂ 🛠️ 🏃 ⚫️. + +## 📦 🖼 + +☁ ✔️ 1️⃣ 👑 🧰 ✍ & 🛠️ **📦 🖼** & **📦**. + +& 📤 📢 ☁ 🎡 ⏮️ 🏤-⚒ **🛂 📦 🖼** 📚 🧰, 🌐, 💽, & 🈸. + +🖼, 📤 🛂 🐍 🖼. + +& 📤 📚 🎏 🖼 🎏 👜 💖 💽, 🖼: + +* +* +* +* , ♒️. + +⚙️ 🏤-⚒ 📦 🖼 ⚫️ 📶 ⏩ **🌀** & ⚙️ 🎏 🧰. 🖼, 🔄 👅 🆕 💽. 🌅 💼, 👆 💪 ⚙️ **🛂 🖼**, & 🔗 👫 ⏮️ 🌐 🔢. + +👈 🌌, 📚 💼 👆 💪 💡 🔃 📦 & ☁ & 🏤-⚙️ 👈 💡 ⏮️ 📚 🎏 🧰 & 🦲. + +, 👆 🔜 🏃 **💗 📦** ⏮️ 🎏 👜, 💖 💽, 🐍 🈸, 🕸 💽 ⏮️ 😥 🕸 🈸, & 🔗 👫 👯‍♂️ 📨 👫 🔗 🕸. + +🌐 📦 🧾 ⚙️ (💖 ☁ ⚖️ Kubernete) ✔️ 👫 🕸 ⚒ 🛠️ 🔘 👫. + +## 📦 & 🛠️ + +**📦 🖼** 🛎 🔌 🚮 🗃 🔢 📋 ⚖️ 📋 👈 🔜 🏃 🕐❔ **📦** ▶️ & 🔢 🚶‍♀️ 👈 📋. 📶 🎏 ⚫️❔ 🔜 🚥 ⚫️ 📋 ⏸. + +🕐❔ **📦** ▶️, ⚫️ 🔜 🏃 👈 📋/📋 (👐 👆 💪 🔐 ⚫️ & ⚒ ⚫️ 🏃 🎏 📋/📋). + +📦 🏃 📏 **👑 🛠️** (📋 ⚖️ 📋) 🏃. + +📦 🛎 ✔️ **👁 🛠️**, ✋️ ⚫️ 💪 ▶️ ✳ ⚪️➡️ 👑 🛠️, & 👈 🌌 👆 🔜 ✔️ **💗 🛠️** 🎏 📦. + +✋️ ⚫️ 🚫 💪 ✔️ 🏃‍♂ 📦 🍵 **🌘 1️⃣ 🏃‍♂ 🛠️**. 🚥 👑 🛠️ ⛔️, 📦 ⛔️. + +## 🏗 ☁ 🖼 FastAPI + +🆗, ➡️ 🏗 🕳 🔜 ❗ 👶 + +👤 🔜 🎦 👆 ❔ 🏗 **☁ 🖼** FastAPI **⚪️➡️ 🖌**, ⚓️ 🔛 **🛂 🐍** 🖼. + +👉 ⚫️❔ 👆 🔜 💚 **🏆 💼**, 🖼: + +* ⚙️ **Kubernete** ⚖️ 🎏 🧰 +* 🕐❔ 🏃‍♂ 🔛 **🍓 👲** +* ⚙️ ☁ 🐕‍🦺 👈 🔜 🏃 📦 🖼 👆, ♒️. + +### 📦 📄 + +👆 🔜 🛎 ✔️ **📦 📄** 👆 🈸 📁. + +⚫️ 🔜 🪀 ✴️ 🔛 🧰 👆 ⚙️ **❎** 👈 📄. + +🌅 ⚠ 🌌 ⚫️ ✔️ 📁 `requirements.txt` ⏮️ 📦 📛 & 👫 ⏬, 1️⃣ 📍 ⏸. + +👆 🔜 ↗️ ⚙️ 🎏 💭 👆 ✍ [🔃 FastAPI ⏬](./versions.md){.internal-link target=_blank} ⚒ ↔ ⏬. + +🖼, 👆 `requirements.txt` 💪 👀 💖: + +``` +fastapi>=0.68.0,<0.69.0 +pydantic>=1.8.0,<2.0.0 +uvicorn>=0.15.0,<0.16.0 +``` + +& 👆 🔜 🛎 ❎ 👈 📦 🔗 ⏮️ `pip`, 🖼: + +
+ +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic uvicorn +``` + +
+ +!!! info + 📤 🎏 📁 & 🧰 🔬 & ❎ 📦 🔗. + + 👤 🔜 🎦 👆 🖼 ⚙️ 🎶 ⏪ 📄 🔛. 👶 + +### ✍ **FastAPI** 📟 + +* ✍ `app` 📁 & ⛔ ⚫️. +* ✍ 🛁 📁 `__init__.py`. +* ✍ `main.py` 📁 ⏮️: + +```Python +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +### 📁 + +🔜 🎏 🏗 📁 ✍ 📁 `Dockerfile` ⏮️: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 + +# (2) +WORKDIR /code + +# (3) +COPY ./requirements.txt /code/requirements.txt + +# (4) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5) +COPY ./app /code/app + +# (6) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1️⃣. ▶️ ⚪️➡️ 🛂 🐍 🧢 🖼. + +2️⃣. ⚒ ⏮️ 👷 📁 `/code`. + + 👉 🌐❔ 👥 🔜 🚮 `requirements.txt` 📁 & `app` 📁. + +3️⃣. 📁 📁 ⏮️ 📄 `/code` 📁. + + 📁 **🕴** 📁 ⏮️ 📄 🥇, 🚫 🎂 📟. + + 👉 📁 **🚫 🔀 🛎**, ☁ 🔜 🔍 ⚫️ & ⚙️ **💾** 👉 🔁, 🛠️ 💾 ⏭ 🔁 💁‍♂️. + +4️⃣. ❎ 📦 🔗 📄 📁. + + `--no-cache-dir` 🎛 💬 `pip` 🚫 🖊 ⏬ 📦 🌐, 👈 🕴 🚥 `pip` 🔜 🏃 🔄 ❎ 🎏 📦, ✋️ 👈 🚫 💼 🕐❔ 👷 ⏮️ 📦. + + !!! note + `--no-cache-dir` 🕴 🔗 `pip`, ⚫️ ✔️ 🕳 ⏮️ ☁ ⚖️ 📦. + + `--upgrade` 🎛 💬 `pip` ♻ 📦 🚥 👫 ⏪ ❎. + + ↩️ ⏮️ 🔁 🖨 📁 💪 🔍 **☁ 💾**, 👉 🔁 🔜 **⚙️ ☁ 💾** 🕐❔ 💪. + + ⚙️ 💾 👉 🔁 🔜 **🖊** 👆 📚 **🕰** 🕐❔ 🏗 🖼 🔄 & 🔄 ⏮️ 🛠️, ↩️ **⏬ & ❎** 🌐 🔗 **🔠 🕰**. + +5️⃣. 📁 `./app` 📁 🔘 `/code` 📁. + + 👉 ✔️ 🌐 📟 ❔ ⚫️❔ **🔀 🌅 🛎** ☁ **💾** 🏆 🚫 ⚙️ 👉 ⚖️ 🙆 **📄 🔁** 💪. + + , ⚫️ ⚠ 🚮 👉 **🏘 🔚** `Dockerfile`, 🔬 📦 🖼 🏗 🕰. + +6️⃣. ⚒ **📋** 🏃 `uvicorn` 💽. + + `CMD` ✊ 📇 🎻, 🔠 👫 🎻 ⚫️❔ 👆 🔜 🆎 📋 ⏸ 👽 🚀. + + 👉 📋 🔜 🏃 ⚪️➡️ **⏮️ 👷 📁**, 🎏 `/code` 📁 👆 ⚒ 🔛 ⏮️ `WORKDIR /code`. + + ↩️ 📋 🔜 ▶️ `/code` & 🔘 ⚫️ 📁 `./app` ⏮️ 👆 📟, **Uvicorn** 🔜 💪 👀 & **🗄** `app` ⚪️➡️ `app.main`. + +!!! tip + 📄 ⚫️❔ 🔠 ⏸ 🔨 🖊 🔠 🔢 💭 📟. 👶 + +👆 🔜 🔜 ✔️ 📁 📊 💖: + +``` +. +├── app +│   ├── __init__.py +│ └── main.py +├── Dockerfile +└── requirements.txt +``` + +#### ⛅ 🤝 ❎ 🗳 + +🚥 👆 🏃‍♂ 👆 📦 ⛅ 🤝 ❎ 🗳 (📐 ⚙) 💖 👌 ⚖️ Traefik, 🚮 🎛 `--proxy-headers`, 👉 🔜 💬 Uvicorn 💙 🎚 📨 👈 🗳 💬 ⚫️ 👈 🈸 🏃 ⛅ 🇺🇸🔍, ♒️. + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +#### ☁ 💾 + +📤 ⚠ 🎱 👉 `Dockerfile`, 👥 🥇 📁 **📁 ⏮️ 🔗 😞**, 🚫 🎂 📟. ➡️ 👤 💬 👆 ⚫️❔ 👈. + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +☁ & 🎏 🧰 **🏗** 👉 📦 🖼 **🔁**, 🚮 **1️⃣ 🧽 🔛 🔝 🎏**, ▶️ ⚪️➡️ 🔝 `Dockerfile` & ❎ 🙆 📁 ✍ 🔠 👩‍🌾 `Dockerfile`. + +☁ & 🎏 🧰 ⚙️ **🔗 💾** 🕐❔ 🏗 🖼, 🚥 📁 🚫 🔀 ↩️ 🏁 🕰 🏗 📦 🖼, ⤴️ ⚫️ 🔜 **🏤-⚙️ 🎏 🧽** ✍ 🏁 🕰, ↩️ 🖨 📁 🔄 & 🏗 🆕 🧽 ⚪️➡️ 🖌. + +❎ 📁 📁 🚫 🎯 📉 👜 💁‍♂️ 🌅, ✋️ ↩️ ⚫️ ⚙️ 💾 👈 🔁, ⚫️ 💪 **⚙️ 💾 ⏭ 🔁**. 🖼, ⚫️ 💪 ⚙️ 💾 👩‍🌾 👈 ❎ 🔗 ⏮️: + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +📁 ⏮️ 📦 📄 **🏆 🚫 🔀 🛎**. , 🖨 🕴 👈 📁, ☁ 🔜 💪 **⚙️ 💾** 👈 🔁. + +& ⤴️, ☁ 🔜 💪 **⚙️ 💾 ⏭ 🔁** 👈 ⏬ & ❎ 👈 🔗. & 📥 🌐❔ 👥 **🖊 📚 🕰**. 👶 ...& ❎ 😩 ⌛. 👶 👶 + +⏬ & ❎ 📦 🔗 **💪 ✊ ⏲**, ✋️ ⚙️ **💾** 🔜 **✊ 🥈** 🌅. + +& 👆 🔜 🏗 📦 🖼 🔄 & 🔄 ⏮️ 🛠️ ✅ 👈 👆 📟 🔀 👷, 📤 📚 📈 🕰 👉 🔜 🖊. + +⤴️, 🏘 🔚 `Dockerfile`, 👥 📁 🌐 📟. 👉 ⚫️❔ **🔀 🏆 🛎**, 👥 🚮 ⚫️ 🏘 🔚, ↩️ 🌖 🕧, 🕳 ⏮️ 👉 🔁 🔜 🚫 💪 ⚙️ 💾. + +```Dockerfile +COPY ./app /code/app +``` + +### 🏗 ☁ 🖼 + +🔜 👈 🌐 📁 🥉, ➡️ 🏗 📦 🖼. + +* 🚶 🏗 📁 (🌐❔ 👆 `Dockerfile` , ⚗ 👆 `app` 📁). +* 🏗 👆 FastAPI 🖼: + +
+ +```console +$ docker build -t myimage . + +---> 100% +``` + +
+ +!!! tip + 👀 `.` 🔚, ⚫️ 🌓 `./`, ⚫️ 💬 ☁ 📁 ⚙️ 🏗 📦 🖼. + + 👉 💼, ⚫️ 🎏 ⏮️ 📁 (`.`). + +### ▶️ ☁ 📦 + +* 🏃 📦 ⚓️ 🔛 👆 🖼: + +
+ +```console +$ docker run -d --name mycontainer -p 80:80 myimage +``` + +
+ +## ✅ ⚫️ + +👆 🔜 💪 ✅ ⚫️ 👆 ☁ 📦 📛, 🖼: http://192.168.99.100/items/5?q=somequery ⚖️ http://127.0.0.1/items/5?q=somequery (⚖️ 🌓, ⚙️ 👆 ☁ 🦠). + +👆 🔜 👀 🕳 💖: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +## 🎓 🛠️ 🩺 + +🔜 👆 💪 🚶 http://192.168.99.100/docs ⚖️ http://127.0.0.1/docs (⚖️ 🌓, ⚙️ 👆 ☁ 🦠). + +👆 🔜 👀 🏧 🎓 🛠️ 🧾 (🚚 🦁 🎚): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +## 🎛 🛠️ 🩺 + +& 👆 💪 🚶 http://192.168.99.100/redoc ⚖️ http://127.0.0.1/redoc (⚖️ 🌓, ⚙️ 👆 ☁ 🦠). + +👆 🔜 👀 🎛 🏧 🧾 (🚚 📄): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## 🏗 ☁ 🖼 ⏮️ 👁-📁 FastAPI + +🚥 👆 FastAPI 👁 📁, 🖼, `main.py` 🍵 `./app` 📁, 👆 📁 📊 💪 👀 💖 👉: + +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` + +⤴️ 👆 🔜 ✔️ 🔀 🔗 ➡ 📁 📁 🔘 `Dockerfile`: + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1) +COPY ./main.py /code/ + +# (2) +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1️⃣. 📁 `main.py` 📁 `/code` 📁 🔗 (🍵 🙆 `./app` 📁). + +2️⃣. 🏃 Uvicorn & 💬 ⚫️ 🗄 `app` 🎚 ⚪️➡️ `main` (↩️ 🏭 ⚪️➡️ `app.main`). + +⤴️ 🔆 Uvicorn 📋 ⚙️ 🆕 🕹 `main` ↩️ `app.main` 🗄 FastAPI 🎚 `app`. + +## 🛠️ 🔧 + +➡️ 💬 🔄 🔃 🎏 [🛠️ 🔧](./concepts.md){.internal-link target=_blank} ⚖ 📦. + +📦 ✴️ 🧰 📉 🛠️ **🏗 & 🛠️** 🈸, ✋️ 👫 🚫 🛠️ 🎯 🎯 🍵 👉 **🛠️ 🔧**, & 📤 📚 💪 🎛. + +**👍 📰** 👈 ⏮️ 🔠 🎏 🎛 📤 🌌 📔 🌐 🛠️ 🔧. 👶 + +➡️ 📄 👉 **🛠️ 🔧** ⚖ 📦: + +* 🇺🇸🔍 +* 🏃‍♂ 🔛 🕴 +* ⏏ +* 🧬 (🔢 🛠️ 🏃) +* 💾 +* ⏮️ 🔁 ⏭ ▶️ + +## 🇺🇸🔍 + +🚥 👥 🎯 🔛 **📦 🖼** FastAPI 🈸 (& ⏪ 🏃‍♂ **📦**), 🇺🇸🔍 🛎 🔜 🍵 **🗜** ➕1️⃣ 🧰. + +⚫️ 💪 ➕1️⃣ 📦, 🖼 ⏮️ Traefik, 🚚 **🇺🇸🔍** & **🏧** 🛠️ **📄**. + +!!! tip + Traefik ✔️ 🛠️ ⏮️ ☁, Kubernete, & 🎏, ⚫️ 📶 ⏩ ⚒ 🆙 & 🔗 🇺🇸🔍 👆 📦 ⏮️ ⚫️. + +👐, 🇺🇸🔍 💪 🍵 ☁ 🐕‍🦺 1️⃣ 👫 🐕‍🦺 (⏪ 🏃 🈸 📦). + +## 🏃‍♂ 🔛 🕴 & ⏏ + +📤 🛎 ➕1️⃣ 🧰 🈚 **▶️ & 🏃‍♂** 👆 📦. + +⚫️ 💪 **☁** 🔗, **☁ ✍**, **Kubernete**, **☁ 🐕‍🦺**, ♒️. + +🌅 (⚖️ 🌐) 💼, 📤 🙅 🎛 🛠️ 🏃 📦 🔛 🕴 & 🛠️ ⏏ 🔛 ❌. 🖼, ☁, ⚫️ 📋 ⏸ 🎛 `--restart`. + +🍵 ⚙️ 📦, ⚒ 🈸 🏃 🔛 🕴 & ⏮️ ⏏ 💪 ⚠ & ⚠. ✋️ 🕐❔ **👷 ⏮️ 📦** 🌅 💼 👈 🛠️ 🔌 🔢. 👶 + +## 🧬 - 🔢 🛠️ + +🚥 👆 ✔️ 🌑 🎰 ⏮️ **☁**, ☁ 🐝 📳, 🖖, ⚖️ ➕1️⃣ 🎏 🏗 ⚙️ 🛠️ 📎 📦 🔛 💗 🎰, ⤴️ 👆 🔜 🎲 💚 **🍵 🧬** **🌑 🎚** ↩️ ⚙️ **🛠️ 👨‍💼** (💖 🐁 ⏮️ 👨‍🏭) 🔠 📦. + +1️⃣ 📚 📎 📦 🧾 ⚙️ 💖 Kubernete 🛎 ✔️ 🛠️ 🌌 🚚 **🧬 📦** ⏪ 🔗 **📐 ⚖** 📨 📨. 🌐 **🌑 🎚**. + +📚 💼, 👆 🔜 🎲 💚 🏗 **☁ 🖼 ⚪️➡️ 🖌** [🔬 🔛](#dockerfile), ❎ 👆 🔗, & 🏃‍♂ **👁 Uvicorn 🛠️** ↩️ 🏃‍♂ 🕳 💖 🐁 ⏮️ Uvicorn 👨‍🏭. + +### 📐 ⚙ + +🕐❔ ⚙️ 📦, 👆 🔜 🛎 ✔️ 🦲 **👂 🔛 👑 ⛴**. ⚫️ 💪 🎲 ➕1️⃣ 📦 👈 **🤝 ❎ 🗳** 🍵 **🇺🇸🔍** ⚖️ 🎏 🧰. + +👉 🦲 🔜 ✊ **📐** 📨 & 📎 👈 👪 👨‍🏭 (🤞) **⚖** 🌌, ⚫️ 🛎 🤙 **📐 ⚙**. + +!!! tip + 🎏 **🤝 ❎ 🗳** 🦲 ⚙️ 🇺🇸🔍 🔜 🎲 **📐 ⚙**. + +& 🕐❔ 👷 ⏮️ 📦, 🎏 ⚙️ 👆 ⚙️ ▶️ & 🛠️ 👫 🔜 ⏪ ✔️ 🔗 🧰 📶 **🕸 📻** (✅ 🇺🇸🔍 📨) ⚪️➡️ 👈 **📐 ⚙** (👈 💪 **🤝 ❎ 🗳**) 📦(Ⓜ) ⏮️ 👆 📱. + +### 1️⃣ 📐 ⚙ - 💗 👨‍🏭 📦 + +🕐❔ 👷 ⏮️ **Kubernete** ⚖️ 🎏 📎 📦 🧾 ⚙️, ⚙️ 👫 🔗 🕸 🛠️ 🔜 ✔ 👁 **📐 ⚙** 👈 👂 🔛 👑 **⛴** 📶 📻 (📨) 🎲 **💗 📦** 🏃 👆 📱. + +🔠 👫 📦 🏃‍♂ 👆 📱 🔜 🛎 ✔️ **1️⃣ 🛠️** (✅ Uvicorn 🛠️ 🏃 👆 FastAPI 🈸). 👫 🔜 🌐 **🌓 📦**, 🏃‍♂ 🎏 👜, ✋️ 🔠 ⏮️ 🚮 👍 🛠️, 💾, ♒️. 👈 🌌 👆 🔜 ✊ 📈 **🛠️** **🎏 🐚** 💽, ⚖️ **🎏 🎰**. + +& 📎 📦 ⚙️ ⏮️ **📐 ⚙** 🔜 **📎 📨** 🔠 1️⃣ 📦 ⏮️ 👆 📱 **🔄**. , 🔠 📨 💪 🍵 1️⃣ 💗 **🔁 📦** 🏃 👆 📱. + +& 🛎 👉 **📐 ⚙** 🔜 💪 🍵 📨 👈 🚶 *🎏* 📱 👆 🌑 (✅ 🎏 🆔, ⚖️ 🔽 🎏 📛 ➡ 🔡), & 🔜 📶 👈 📻 ▶️️ 📦 *👈 🎏* 🈸 🏃‍♂ 👆 🌑. + +### 1️⃣ 🛠️ 📍 📦 + +👉 🆎 😐, 👆 🎲 🔜 💚 ✔️ **👁 (Uvicorn) 🛠️ 📍 📦**, 👆 🔜 ⏪ 🚚 🧬 🌑 🎚. + +, 👉 💼, 👆 **🔜 🚫** 💚 ✔️ 🛠️ 👨‍💼 💖 🐁 ⏮️ Uvicorn 👨‍🏭, ⚖️ Uvicorn ⚙️ 🚮 👍 Uvicorn 👨‍🏭. 👆 🔜 💚 ✔️ **👁 Uvicorn 🛠️** 📍 📦 (✋️ 🎲 💗 📦). + +✔️ ➕1️⃣ 🛠️ 👨‍💼 🔘 📦 (🔜 ⏮️ 🐁 ⚖️ Uvicorn 🛠️ Uvicorn 👨‍🏭) 🔜 🕴 🚮 **🙃 🔀** 👈 👆 🌅 🎲 ⏪ ✊ 💅 ⏮️ 👆 🌑 ⚙️. + +### 📦 ⏮️ 💗 🛠️ & 🎁 💼 + +↗️, 📤 **🎁 💼** 🌐❔ 👆 💪 💚 ✔️ **📦** ⏮️ **🐁 🛠️ 👨‍💼** ▶️ 📚 **Uvicorn 👨‍🏭 🛠️** 🔘. + +📚 💼, 👆 💪 ⚙️ **🛂 ☁ 🖼** 👈 🔌 **🐁** 🛠️ 👨‍💼 🏃‍♂ 💗 **Uvicorn 👨‍🏭 🛠️**, & 🔢 ⚒ 🔆 🔢 👨‍🏭 ⚓️ 🔛 ⏮️ 💽 🐚 🔁. 👤 🔜 💬 👆 🌅 🔃 ⚫️ 🔛 [🛂 ☁ 🖼 ⏮️ 🐁 - Uvicorn](#official-docker-image-with-gunicorn-uvicorn). + +📥 🖼 🕐❔ 👈 💪 ⚒ 🔑: + +#### 🙅 📱 + +👆 💪 💚 🛠️ 👨‍💼 📦 🚥 👆 🈸 **🙅 🥃** 👈 👆 🚫 💪 (🐥 🚫) 👌-🎶 🔢 🛠️ 💁‍♂️ 🌅, & 👆 💪 ⚙️ 🏧 🔢 (⏮️ 🛂 ☁ 🖼), & 👆 🏃‍♂ ⚫️ 🔛 **👁 💽**, 🚫 🌑. + +#### ☁ ✍ + +👆 💪 🛠️ **👁 💽** (🚫 🌑) ⏮️ **☁ ✍**, 👆 🚫🔜 ✔️ ⏩ 🌌 🛠️ 🧬 📦 (⏮️ ☁ ✍) ⏪ 🛡 🔗 🕸 & **📐 ⚖**. + +⤴️ 👆 💪 💚 ✔️ **👁 📦** ⏮️ **🛠️ 👨‍💼** ▶️ **📚 👨‍🏭 🛠️** 🔘. + +#### 🤴 & 🎏 🤔 + +👆 💪 ✔️ **🎏 🤔** 👈 🔜 ⚒ ⚫️ ⏩ ✔️ **👁 📦** ⏮️ **💗 🛠️** ↩️ ✔️ **💗 📦** ⏮️ **👁 🛠️** 🔠 👫. + +🖼 (🪀 🔛 👆 🖥) 👆 💪 ✔️ 🧰 💖 🤴 🏭 🎏 📦 👈 🔜 ✔️ 🔐 **🔠 📨** 👈 👟. + +👉 💼, 🚥 👆 ✔️ **💗 📦**, 🔢, 🕐❔ 🤴 👟 **✍ ⚖**, ⚫️ 🔜 🤚 🕐 **👁 📦 🔠 🕰** (📦 👈 🍵 👈 🎯 📨), ↩️ 🤚 **📈 ⚖** 🌐 🔁 📦. + +⤴️, 👈 💼, ⚫️ 💪 🙅 ✔️ **1️⃣ 📦** ⏮️ **💗 🛠️**, & 🇧🇿 🧰 (✅ 🤴 🏭) 🔛 🎏 📦 📈 🤴 ⚖ 🌐 🔗 🛠️ & 🎦 👈 ⚖ 🔛 👈 👁 📦. + +--- + +👑 ☝, **👌** 👉 **🚫 ✍ 🗿** 👈 👆 ✔️ 😄 ⏩. 👆 💪 ⚙️ 👫 💭 **🔬 👆 👍 ⚙️ 💼** & 💭 ⚫️❔ 👍 🎯 👆 ⚙️, ✅ 👅 ❔ 🛠️ 🔧: + +* 💂‍♂ - 🇺🇸🔍 +* 🏃‍♂ 🔛 🕴 +* ⏏ +* 🧬 (🔢 🛠️ 🏃) +* 💾 +* ⏮️ 🔁 ⏭ ▶️ + +## 💾 + +🚥 👆 🏃 **👁 🛠️ 📍 📦** 👆 🔜 ✔️ 🌅 ⚖️ 🌘 👍-🔬, ⚖, & 📉 💸 💾 🍴 🔠 👈 📦 (🌅 🌘 1️⃣ 🚥 👫 🔁). + +& ⤴️ 👆 💪 ⚒ 👈 🎏 💾 📉 & 📄 👆 📳 👆 📦 🧾 ⚙️ (🖼 **Kubernete**). 👈 🌌 ⚫️ 🔜 💪 **🔁 📦** **💪 🎰** ✊ 🔘 🏧 💸 💾 💪 👫, & 💸 💪 🎰 🌑. + +🚥 👆 🈸 **🙅**, 👉 🔜 🎲 **🚫 ⚠**, & 👆 💪 🚫 💪 ✔ 🏋️ 💾 📉. ✋️ 🚥 👆 **⚙️ 📚 💾** (🖼 ⏮️ **🎰 🏫** 🏷), 👆 🔜 ✅ ❔ 🌅 💾 👆 😩 & 🔆 **🔢 📦** 👈 🏃 **🔠 🎰** (& 🎲 🚮 🌖 🎰 👆 🌑). + +🚥 👆 🏃 **💗 🛠️ 📍 📦** (🖼 ⏮️ 🛂 ☁ 🖼) 👆 🔜 ✔️ ⚒ 💭 👈 🔢 🛠️ ▶️ 🚫 **🍴 🌖 💾** 🌘 ⚫️❔ 💪. + +## ⏮️ 🔁 ⏭ ▶️ & 📦 + +🚥 👆 ⚙️ 📦 (✅ ☁, Kubernete), ⤴️ 📤 2️⃣ 👑 🎯 👆 💪 ⚙️. + +### 💗 📦 + +🚥 👆 ✔️ **💗 📦**, 🎲 🔠 1️⃣ 🏃 **👁 🛠️** (🖼, **Kubernete** 🌑), ⤴️ 👆 🔜 🎲 💚 ✔️ **🎏 📦** 🔨 👷 **⏮️ 📶** 👁 📦, 🏃 👁 🛠️, **⏭** 🏃 🔁 👨‍🏭 📦. + +!!! info + 🚥 👆 ⚙️ Kubernete, 👉 🔜 🎲 🕑 📦. + +🚥 👆 ⚙️ 💼 📤 🙅‍♂ ⚠ 🏃‍♂ 👈 ⏮️ 📶 **💗 🕰 🔗** (🖼 🚥 👆 🚫 🏃 💽 🛠️, ✋️ ✅ 🚥 💽 🔜), ⤴️ 👆 💪 🚮 👫 🔠 📦 ▶️️ ⏭ ▶️ 👑 🛠️. + +### 👁 📦 + +🚥 👆 ✔️ 🙅 🖥, ⏮️ **👁 📦** 👈 ⤴️ ▶️ 💗 **👨‍🏭 🛠️** (⚖️ 1️⃣ 🛠️), ⤴️ 👆 💪 🏃 👈 ⏮️ 🔁 🎏 📦, ▶️️ ⏭ ▶️ 🛠️ ⏮️ 📱. 🛂 ☁ 🖼 🐕‍🦺 👉 🔘. + +## 🛂 ☁ 🖼 ⏮️ 🐁 - Uvicorn + +📤 🛂 ☁ 🖼 👈 🔌 🐁 🏃‍♂ ⏮️ Uvicorn 👨‍🏭, ℹ ⏮️ 📃: [💽 👨‍🏭 - 🐁 ⏮️ Uvicorn](./server-workers.md){.internal-link target=_blank}. + +👉 🖼 🔜 ⚠ ✴️ ⚠ 🔬 🔛: [📦 ⏮️ 💗 🛠️ & 🎁 💼](#containers-with-multiple-processes-and-special-cases). + +* tiangolo/uvicorn-🐁-fastapi. + +!!! warning + 📤 ↕ 🤞 👈 👆 **🚫** 💪 👉 🧢 🖼 ⚖️ 🙆 🎏 🎏 1️⃣, & 🔜 👻 📆 🏗 🖼 ⚪️➡️ 🖌 [🔬 🔛: 🏗 ☁ 🖼 FastAPI](#build-a-docker-image-for-fastapi). + +👉 🖼 ✔️ **🚘-📳** 🛠️ 🔌 ⚒ **🔢 👨‍🏭 🛠️** ⚓️ 🔛 💽 🐚 💪. + +⚫️ ✔️ **🤔 🔢**, ✋️ 👆 💪 🔀 & ℹ 🌐 📳 ⏮️ **🌐 🔢** ⚖️ 📳 📁. + +⚫️ 🐕‍🦺 🏃 **⏮️ 🔁 ⏭ ▶️** ⏮️ ✍. + +!!! tip + 👀 🌐 📳 & 🎛, 🚶 ☁ 🖼 📃: Tiangolo/uvicorn-🐁-fastapi. + +### 🔢 🛠️ 🔛 🛂 ☁ 🖼 + +**🔢 🛠️** 🔛 👉 🖼 **📊 🔁** ⚪️➡️ 💽 **🐚** 💪. + +👉 ⛓ 👈 ⚫️ 🔜 🔄 **🗜** 🌅 **🎭** ⚪️➡️ 💽 💪. + +👆 💪 🔆 ⚫️ ⏮️ 📳 ⚙️ **🌐 🔢**, ♒️. + +✋️ ⚫️ ⛓ 👈 🔢 🛠️ 🪀 🔛 💽 📦 🏃, **💸 💾 🍴** 🔜 🪀 🔛 👈. + +, 🚥 👆 🈸 🍴 📚 💾 (🖼 ⏮️ 🎰 🏫 🏷), & 👆 💽 ✔️ 📚 💽 🐚 **✋️ 🐥 💾**, ⤴️ 👆 📦 💪 🔚 🆙 🔄 ⚙️ 🌅 💾 🌘 ⚫️❔ 💪, & 🤕 🎭 📚 (⚖️ 💥). 👶 + +### ✍ `Dockerfile` + +📥 ❔ 👆 🔜 ✍ `Dockerfile` ⚓️ 🔛 👉 🖼: + +```Dockerfile +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app +``` + +### 🦏 🈸 + +🚥 👆 ⏩ 📄 🔃 🏗 [🦏 🈸 ⏮️ 💗 📁](../tutorial/bigger-applications.md){.internal-link target=_blank}, 👆 `Dockerfile` 💪 ↩️ 👀 💖: + +```Dockerfile hl_lines="7" +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app/app +``` + +### 🕐❔ ⚙️ + +👆 🔜 🎲 **🚫** ⚙️ 👉 🛂 🧢 🖼 (⚖️ 🙆 🎏 🎏 1️⃣) 🚥 👆 ⚙️ **Kubernete** (⚖️ 🎏) & 👆 ⏪ ⚒ **🧬** 🌑 🎚, ⏮️ 💗 **📦**. 📚 💼, 👆 👍 📆 **🏗 🖼 ⚪️➡️ 🖌** 🔬 🔛: [🏗 ☁ 🖼 FastAPI](#build-a-docker-image-for-fastapi). + +👉 🖼 🔜 ⚠ ✴️ 🎁 💼 🔬 🔛 [📦 ⏮️ 💗 🛠️ & 🎁 💼](#containers-with-multiple-processes-and-special-cases). 🖼, 🚥 👆 🈸 **🙅 🥃** 👈 ⚒ 🔢 🔢 🛠️ ⚓️ 🔛 💽 👷 👍, 👆 🚫 💚 😥 ⏮️ ❎ 🛠️ 🧬 🌑 🎚, & 👆 🚫 🏃 🌅 🌘 1️⃣ 📦 ⏮️ 👆 📱. ⚖️ 🚥 👆 🛠️ ⏮️ **☁ ✍**, 🏃 🔛 👁 💽, ♒️. + +## 🛠️ 📦 🖼 + +⏮️ ✔️ 📦 (☁) 🖼 📤 📚 🌌 🛠️ ⚫️. + +🖼: + +* ⏮️ **☁ ✍** 👁 💽 +* ⏮️ **Kubernete** 🌑 +* ⏮️ ☁ 🐝 📳 🌑 +* ⏮️ ➕1️⃣ 🧰 💖 🖖 +* ⏮️ ☁ 🐕‍🦺 👈 ✊ 👆 📦 🖼 & 🛠️ ⚫️ + +## ☁ 🖼 ⏮️ 🎶 + +🚥 👆 ⚙️ 🎶 🛠️ 👆 🏗 🔗, 👆 💪 ⚙️ ☁ 👁-▶️ 🏗: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 as requirements-stage + +# (2) +WORKDIR /tmp + +# (3) +RUN pip install poetry + +# (4) +COPY ./pyproject.toml ./poetry.lock* /tmp/ + +# (5) +RUN poetry export -f requirements.txt --output requirements.txt --without-hashes + +# (6) +FROM python:3.9 + +# (7) +WORKDIR /code + +# (8) +COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt + +# (9) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (10) +COPY ./app /code/app + +# (11) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1️⃣. 👉 🥇 ▶️, ⚫️ 🌟 `requirements-stage`. + +2️⃣. ⚒ `/tmp` ⏮️ 👷 📁. + + 📥 🌐❔ 👥 🔜 🏗 📁 `requirements.txt` + +3️⃣. ❎ 🎶 👉 ☁ ▶️. + +4️⃣. 📁 `pyproject.toml` & `poetry.lock` 📁 `/tmp` 📁. + + ↩️ ⚫️ ⚙️ `./poetry.lock*` (▶️ ⏮️ `*`), ⚫️ 🏆 🚫 💥 🚥 👈 📁 🚫 💪. + +5️⃣. 🏗 `requirements.txt` 📁. + +6️⃣. 👉 🏁 ▶️, 🕳 📥 🔜 🛡 🏁 📦 🖼. + +7️⃣. ⚒ ⏮️ 👷 📁 `/code`. + +8️⃣. 📁 `requirements.txt` 📁 `/code` 📁. + + 👉 📁 🕴 🖖 ⏮️ ☁ ▶️, 👈 ⚫️❔ 👥 ⚙️ `--from-requirements-stage` 📁 ⚫️. + +9️⃣. ❎ 📦 🔗 🏗 `requirements.txt` 📁. + +1️⃣0️⃣. 📁 `app` 📁 `/code` 📁. + +1️⃣1️⃣. 🏃 `uvicorn` 📋, 💬 ⚫️ ⚙️ `app` 🎚 🗄 ⚪️➡️ `app.main`. + +!!! tip + 🖊 💭 🔢 👀 ⚫️❔ 🔠 ⏸ 🔨. + +**☁ ▶️** 🍕 `Dockerfile` 👈 👷 **🍕 📦 🖼** 👈 🕴 ⚙️ 🏗 📁 ⚙️ ⏪. + +🥇 ▶️ 🔜 🕴 ⚙️ **❎ 🎶** & **🏗 `requirements.txt`** ⏮️ 👆 🏗 🔗 ⚪️➡️ 🎶 `pyproject.toml` 📁. + +👉 `requirements.txt` 📁 🔜 ⚙️ ⏮️ `pip` ⏪ **⏭ ▶️**. + +🏁 📦 🖼 **🕴 🏁 ▶️** 🛡. ⏮️ ▶️(Ⓜ) 🔜 ❎. + +🕐❔ ⚙️ 🎶, ⚫️ 🔜 ⚒ 🔑 ⚙️ **☁ 👁-▶️ 🏗** ↩️ 👆 🚫 🤙 💪 ✔️ 🎶 & 🚮 🔗 ❎ 🏁 📦 🖼, 👆 **🕴 💪** ✔️ 🏗 `requirements.txt` 📁 ❎ 👆 🏗 🔗. + +⤴️ ⏭ (& 🏁) ▶️ 👆 🔜 🏗 🖼 🌅 ⚖️ 🌘 🎏 🌌 🔬 ⏭. + +### ⛅ 🤝 ❎ 🗳 - 🎶 + +🔄, 🚥 👆 🏃‍♂ 👆 📦 ⛅ 🤝 ❎ 🗳 (📐 ⚙) 💖 👌 ⚖️ Traefik, 🚮 🎛 `--proxy-headers` 📋: + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +## 🌃 + +⚙️ 📦 ⚙️ (✅ ⏮️ **☁** & **Kubernete**) ⚫️ ▶️️ 📶 🎯 🍵 🌐 **🛠️ 🔧**: + +* 🇺🇸🔍 +* 🏃‍♂ 🔛 🕴 +* ⏏ +* 🧬 (🔢 🛠️ 🏃) +* 💾 +* ⏮️ 🔁 ⏭ ▶️ + +🌅 💼, 👆 🎲 🏆 🚫 💚 ⚙️ 🙆 🧢 🖼, & ↩️ **🏗 📦 🖼 ⚪️➡️ 🖌** 1️⃣ ⚓️ 🔛 🛂 🐍 ☁ 🖼. + +✊ 💅 **✔** 👩‍🌾 `Dockerfile` & **☁ 💾** 👆 💪 **📉 🏗 🕰**, 📉 👆 📈 (& ❎ 😩). 👶 + +🎯 🎁 💼, 👆 💪 💚 ⚙️ 🛂 ☁ 🖼 FastAPI. 👶 diff --git a/docs/em/docs/deployment/https.md b/docs/em/docs/deployment/https.md new file mode 100644 index 0000000000..3feb3a2c2c --- /dev/null +++ b/docs/em/docs/deployment/https.md @@ -0,0 +1,190 @@ +# 🔃 🇺🇸🔍 + +⚫️ ⏩ 🤔 👈 🇺🇸🔍 🕳 👈 "🛠️" ⚖️ 🚫. + +✋️ ⚫️ 🌌 🌖 🏗 🌘 👈. + +!!! tip + 🚥 👆 🏃 ⚖️ 🚫 💅, 😣 ⏮️ ⏭ 📄 🔁 🔁 👩‍🌾 ⚒ 🌐 🆙 ⏮️ 🎏 ⚒. + +**💡 🔰 🇺🇸🔍**, ⚪️➡️ 🏬 🤔, ✅ https://howhttps.works/. + +🔜, ⚪️➡️ **👩‍💻 🤔**, 📥 📚 👜 ✔️ 🤯 ⏪ 💭 🔃 🇺🇸🔍: + +* 🇺🇸🔍, **💽** 💪 **✔️ "📄"** 🏗 **🥉 🥳**. + * 📚 📄 🤙 **🏆** ⚪️➡️ 🥉 🥳, 🚫 "🏗". +* 📄 ✔️ **1️⃣2️⃣🗓️**. + * 👫 **🕛**. + * & ⤴️ 👫 💪 **♻**, **🏆 🔄** ⚪️➡️ 🥉 🥳. +* 🔐 🔗 🔨 **🕸 🎚**. + * 👈 1️⃣ 🧽 **🔛 🇺🇸🔍**. + * , **📄 & 🔐** 🍵 🔨 **⏭ 🇺🇸🔍**. +* **🕸 🚫 💭 🔃 "🆔"**. 🕴 🔃 📢 📢. + * ℹ 🔃 **🎯 🆔** 📨 🚶 **🇺🇸🔍 💽**. +* **🇺🇸🔍 📄** "✔" **🎯 🆔**, ✋️ 🛠️ & 🔐 🔨 🕸 🎚, **⏭ 💭** ❔ 🆔 ➖ 🙅 ⏮️. +* **🔢**, 👈 🔜 ⛓ 👈 👆 💪 🕴 ✔️ **1️⃣ 🇺🇸🔍 📄 📍 📢 📢**. + * 🙅‍♂ 🤔 ❔ 🦏 👆 💽 ⚖️ ❔ 🤪 🔠 🈸 👆 ✔️ 🔛 ⚫️ 💪. + * 📤 **⚗** 👉, 👐. +* 📤 **↔** **🤝** 🛠️ (1️⃣ 🚚 🔐 🕸 🎚, ⏭ 🇺🇸🔍) 🤙 **👲**. + * 👉 👲 ↔ ✔ 1️⃣ 👁 💽 (⏮️ **👁 📢 📢**) ✔️ **📚 🇺🇸🔍 📄** & 🍦 **💗 🇺🇸🔍 🆔/🈸**. + * 👉 👷, **👁** 🦲 (📋) 🏃 🔛 💽, 👂 🔛 **📢 📢 📢**, 🔜 ✔️ **🌐 🇺🇸🔍 📄** 💽. +* **⏮️** 🏆 🔐 🔗, 📻 🛠️ **🇺🇸🔍**. + * 🎚 **🗜**, ✋️ 👫 ➖ 📨 ⏮️ **🇺🇸🔍 🛠️**. + +⚫️ ⚠ 💡 ✔️ **1️⃣ 📋/🇺🇸🔍 💽** 🏃 🔛 💽 (🎰, 🦠, ♒️.) & **🛠️ 🌐 🇺🇸🔍 🍕**: 📨 **🗜 🇺🇸🔍 📨**, 📨 **🗜 🇺🇸🔍 📨** ☑ 🇺🇸🔍 🈸 🏃 🎏 💽 ( **FastAPI** 🈸, 👉 💼), ✊ **🇺🇸🔍 📨** ⚪️➡️ 🈸, **🗜 ⚫️** ⚙️ ☑ **🇺🇸🔍 📄** & 📨 ⚫️ 🔙 👩‍💻 ⚙️ **🇺🇸🔍**. 👉 💽 🛎 🤙 **🤝 ❎ 🗳**. + +🎛 👆 💪 ⚙️ 🤝 ❎ 🗳: + +* Traefik (👈 💪 🍵 📄 🔕) +* 📥 (👈 💪 🍵 📄 🔕) +* 👌 +* ✳ + +## ➡️ 🗜 + +⏭ ➡️ 🗜, 👫 **🇺🇸🔍 📄** 💲 💙 🥉 🥳. + +🛠️ 📎 1️⃣ 👫 📄 ⚙️ ⚠, 🚚 📠 & 📄 😥. + +✋️ ⤴️ **➡️ 🗜** ✍. + +⚫️ 🏗 ⚪️➡️ 💾 🏛. ⚫️ 🚚 **🇺🇸🔍 📄 🆓**, 🏧 🌌. 👫 📄 ⚙️ 🌐 🐩 🔐 💂‍♂, & 📏-🖖 (🔃 3️⃣ 🗓️), **💂‍♂ 🤙 👍** ↩️ 👫 📉 🔆. + +🆔 🔐 ✔ & 📄 🏗 🔁. 👉 ✔ 🏧 🔕 👫 📄. + +💭 🏧 🛠️ & 🔕 👫 📄 👈 👆 💪 ✔️ **🔐 🇺🇸🔍, 🆓, ♾**. + +## 🇺🇸🔍 👩‍💻 + +📥 🖼 ❔ 🇺🇸🔍 🛠️ 💪 👀 💖, 🔁 🔁, 💸 🙋 ✴️ 💭 ⚠ 👩‍💻. + +### 🆔 📛 + +⚫️ 🔜 🎲 🌐 ▶️ 👆 **🏗** **🆔 📛**. ⤴️, 👆 🔜 🔗 ⚫️ 🏓 💽 (🎲 👆 🎏 ☁ 🐕‍🦺). + +👆 🔜 🎲 🤚 ☁ 💽 (🕹 🎰) ⚖️ 🕳 🎏, & ⚫️ 🔜 ✔️ 🔧 **📢 📢 📢**. + +🏓 💽(Ⓜ) 👆 🔜 🔗 ⏺ ("`A record`") ☝ **👆 🆔** 📢 **📢 📢 👆 💽**. + +👆 🔜 🎲 👉 🕐, 🥇 🕰, 🕐❔ ⚒ 🌐 🆙. + +!!! tip + 👉 🆔 📛 🍕 🌌 ⏭ 🇺🇸🔍, ✋️ 🌐 🪀 🔛 🆔 & 📢 📢, ⚫️ 💸 💬 ⚫️ 📥. + +### 🏓 + +🔜 ➡️ 🎯 🔛 🌐 ☑ 🇺🇸🔍 🍕. + +🥇, 🖥 🔜 ✅ ⏮️ **🏓 💽** ⚫️❔ **📢 🆔**, 👉 💼, `someapp.example.com`. + +🏓 💽 🔜 💬 🖥 ⚙️ 🎯 **📢 📢**. 👈 🔜 📢 📢 📢 ⚙️ 👆 💽, 👈 👆 🔗 🏓 💽. + + + +### 🤝 🤝 ▶️ + +🖥 🔜 ⤴️ 🔗 ⏮️ 👈 📢 📢 🔛 **⛴ 4️⃣4️⃣3️⃣** (🇺🇸🔍 ⛴). + +🥇 🍕 📻 🛠️ 🔗 🖖 👩‍💻 & 💽 & 💭 🔐 🔑 👫 🔜 ⚙️, ♒️. + + + +👉 🔗 🖖 👩‍💻 & 💽 🛠️ 🤝 🔗 🤙 **🤝 🤝**. + +### 🤝 ⏮️ 👲 ↔ + +**🕴 1️⃣ 🛠️** 💽 💪 👂 🔛 🎯 **⛴** 🎯 **📢 📢**. 📤 💪 🎏 🛠️ 👂 🔛 🎏 ⛴ 🎏 📢 📢, ✋️ 🕴 1️⃣ 🔠 🌀 📢 📢 & ⛴. + +🤝 (🇺🇸🔍) ⚙️ 🎯 ⛴ `443` 🔢. 👈 ⛴ 👥 🔜 💪. + +🕴 1️⃣ 🛠️ 💪 👂 🔛 👉 ⛴, 🛠️ 👈 🔜 ⚫️ 🔜 **🤝 ❎ 🗳**. + +🤝 ❎ 🗳 🔜 ✔️ 🔐 1️⃣ ⚖️ 🌅 **🤝 📄** (🇺🇸🔍 📄). + +⚙️ **👲 ↔** 🔬 🔛, 🤝 ❎ 🗳 🔜 ✅ ❔ 🤝 (🇺🇸🔍) 📄 💪 ⚫️ 🔜 ⚙️ 👉 🔗, ⚙️ 1️⃣ 👈 🏏 🆔 📈 👩‍💻. + +👉 💼, ⚫️ 🔜 ⚙️ 📄 `someapp.example.com`. + + + +👩‍💻 ⏪ **💙** 👨‍💼 👈 🏗 👈 🤝 📄 (👉 💼 ➡️ 🗜, ✋️ 👥 🔜 👀 🔃 👈 ⏪), ⚫️ 💪 **✔** 👈 📄 ☑. + +⤴️, ⚙️ 📄, 👩‍💻 & 🤝 ❎ 🗳 **💭 ❔ 🗜** 🎂 **🕸 📻**. 👉 🏁 **🤝 🤝** 🍕. + +⏮️ 👉, 👩‍💻 & 💽 ✔️ **🗜 🕸 🔗**, 👉 ⚫️❔ 🤝 🚚. & ⤴️ 👫 💪 ⚙️ 👈 🔗 ▶️ ☑ **🇺🇸🔍 📻**. + +& 👈 ⚫️❔ **🇺🇸🔍** , ⚫️ ✅ **🇺🇸🔍** 🔘 **🔐 🤝 🔗** ↩️ 😁 (💽) 🕸 🔗. + +!!! tip + 👀 👈 🔐 📻 🔨 **🕸 🎚**, 🚫 🇺🇸🔍 🎚. + +### 🇺🇸🔍 📨 + +🔜 👈 👩‍💻 & 💽 (🎯 🖥 & 🤝 ❎ 🗳) ✔️ **🗜 🕸 🔗**, 👫 💪 ▶️ **🇺🇸🔍 📻**. + +, 👩‍💻 📨 **🇺🇸🔍 📨**. 👉 🇺🇸🔍 📨 🔘 🗜 🤝 🔗. + + + +### 🗜 📨 + +🤝 ❎ 🗳 🔜 ⚙️ 🔐 ✔ **🗜 📨**, & 🔜 📶 **✅ (🗜) 🇺🇸🔍 📨** 🛠️ 🏃 🈸 (🖼 🛠️ ⏮️ Uvicorn 🏃‍♂ FastAPI 🈸). + + + +### 🇺🇸🔍 📨 + +🈸 🔜 🛠️ 📨 & 📨 **✅ (💽) 🇺🇸🔍 📨** 🤝 ❎ 🗳. + + + +### 🇺🇸🔍 📨 + +🤝 ❎ 🗳 🔜 ⤴️ **🗜 📨** ⚙️ ⚛ ✔ ⏭ (👈 ▶️ ⏮️ 📄 `someapp.example.com`), & 📨 ⚫️ 🔙 🖥. + +⏭, 🖥 🔜 ✔ 👈 📨 ☑ & 🗜 ⏮️ ▶️️ 🔐 🔑, ♒️. ⚫️ 🔜 ⤴️ **🗜 📨** & 🛠️ ⚫️. + + + +👩‍💻 (🖥) 🔜 💭 👈 📨 👟 ⚪️➡️ ☑ 💽 ↩️ ⚫️ ⚙️ ⚛ 👫 ✔ ⚙️ **🇺🇸🔍 📄** ⏭. + +### 💗 🈸 + +🎏 💽 (⚖️ 💽), 📤 💪 **💗 🈸**, 🖼, 🎏 🛠️ 📋 ⚖️ 💽. + +🕴 1️⃣ 🛠️ 💪 🚚 🎯 📢 & ⛴ (🤝 ❎ 🗳 👆 🖼) ✋️ 🎏 🈸/🛠️ 💪 🏃 🔛 💽(Ⓜ) 💁‍♂️, 📏 👫 🚫 🔄 ⚙️ 🎏 **🌀 📢 📢 & ⛴**. + + + +👈 🌌, 🤝 ❎ 🗳 💪 🍵 🇺🇸🔍 & 📄 **💗 🆔**, 💗 🈸, & ⤴️ 📶 📨 ▶️️ 🈸 🔠 💼. + +### 📄 🔕 + +☝ 🔮, 🔠 📄 🔜 **🕛** (🔃 3️⃣ 🗓️ ⏮️ 🏗 ⚫️). + +& ⤴️, 📤 🔜 ➕1️⃣ 📋 (💼 ⚫️ ➕1️⃣ 📋, 💼 ⚫️ 💪 🎏 🤝 ❎ 🗳) 👈 🔜 💬 ➡️ 🗜, & ♻ 📄(Ⓜ). + + + +**🤝 📄** **🔗 ⏮️ 🆔 📛**, 🚫 ⏮️ 📢 📢. + +, ♻ 📄, 🔕 📋 💪 **🎦** 🛃 (➡️ 🗜) 👈 ⚫️ 👐 **"👍" & 🎛 👈 🆔**. + +👈, & 🏗 🎏 🈸 💪, 📤 📚 🌌 ⚫️ 💪 ⚫️. 🌟 🌌: + +* **🔀 🏓 ⏺**. + * 👉, 🔕 📋 💪 🐕‍🦺 🔗 🏓 🐕‍🦺,, ⚓️ 🔛 🏓 🐕‍🦺 👆 ⚙️, 👉 5️⃣📆 ⚖️ 💪 🚫 🎛. +* **🏃 💽** (🌘 ⏮️ 📄 🛠️ 🛠️) 🔛 📢 📢 📢 🔗 ⏮️ 🆔. + * 👥 💬 🔛, 🕴 1️⃣ 🛠️ 💪 👂 🔛 🎯 📢 & ⛴. + * 👉 1️⃣ 🤔 ⚫️❔ ⚫️ 📶 ⚠ 🕐❔ 🎏 🤝 ❎ 🗳 ✊ 💅 📄 🔕 🛠️. + * ⏪, 👆 💪 ✔️ ⛔️ 🤝 ❎ 🗳 😖, ▶️ 🔕 📋 📎 📄, ⤴️ 🔗 👫 ⏮️ 🤝 ❎ 🗳, & ⤴️ ⏏ 🤝 ❎ 🗳. 👉 🚫 💯, 👆 📱(Ⓜ) 🔜 🚫 💪 ⏮️ 🕰 👈 🤝 ❎ 🗳 📆. + +🌐 👉 🔕 🛠️, ⏪ 🍦 📱, 1️⃣ 👑 🤔 ⚫️❔ 👆 🔜 💚 ✔️ **🎏 ⚙️ 🍵 🇺🇸🔍** ⏮️ 🤝 ❎ 🗳 ↩️ ⚙️ 🤝 📄 ⏮️ 🈸 💽 🔗 (✅ Uvicorn). + +## 🌃 + +✔️ **🇺🇸🔍** 📶 ⚠, & **🎯** 🏆 💼. 🌅 🎯 👆 👩‍💻 ✔️ 🚮 🤭 🇺🇸🔍 🔃 **🤔 👉 🔧** & ❔ 👫 👷. + +✋️ 🕐 👆 💭 🔰 ℹ **🇺🇸🔍 👩‍💻** 👆 💪 💪 🌀 & 🔗 🎏 🧰 ℹ 👆 🛠️ 🌐 🙅 🌌. + +⏭ 📃, 👤 🔜 🎦 👆 📚 🧱 🖼 ❔ ⚒ 🆙 **🇺🇸🔍** **FastAPI** 🈸. 👶 diff --git a/docs/em/docs/deployment/index.md b/docs/em/docs/deployment/index.md new file mode 100644 index 0000000000..9bcf427b69 --- /dev/null +++ b/docs/em/docs/deployment/index.md @@ -0,0 +1,21 @@ +# 🛠️ + +🛠️ **FastAPI** 🈸 📶 ⏩. + +## ⚫️❔ 🔨 🛠️ ⛓ + +**🛠️** 🈸 ⛓ 🎭 💪 📶 ⚒ ⚫️ **💪 👩‍💻**. + +**🕸 🛠️**, ⚫️ 🛎 🔌 🚮 ⚫️ **🛰 🎰**, ⏮️ **💽 📋** 👈 🚚 👍 🎭, ⚖, ♒️, 👈 👆 **👩‍💻** 💪 **🔐** 🈸 ♻ & 🍵 🔁 ⚖️ ⚠. + +👉 🔅 **🛠️** ▶️, 🌐❔ 👆 🕧 🔀 📟, 💔 ⚫️ & ♻ ⚫️, ⛔️ & 🔁 🛠️ 💽, ♒️. + +## 🛠️ 🎛 + +📤 📚 🌌 ⚫️ ⚓️ 🔛 👆 🎯 ⚙️ 💼 & 🧰 👈 👆 ⚙️. + +👆 💪 **🛠️ 💽** 👆 ⚙️ 🌀 🧰, 👆 💪 ⚙️ **☁ 🐕‍🦺** 👈 🔨 🍕 👷 👆, ⚖️ 🎏 💪 🎛. + +👤 🔜 🎦 👆 👑 🔧 👆 🔜 🎲 ✔️ 🤯 🕐❔ 🛠️ **FastAPI** 🈸 (👐 🌅 ⚫️ ✔ 🙆 🎏 🆎 🕸 🈸). + +👆 🔜 👀 🌖 ℹ ✔️ 🤯 & ⚒ ⚫️ ⏭ 📄. 👶 diff --git a/docs/em/docs/deployment/manually.md b/docs/em/docs/deployment/manually.md new file mode 100644 index 0000000000..f27b423e24 --- /dev/null +++ b/docs/em/docs/deployment/manually.md @@ -0,0 +1,145 @@ +# 🏃 💽 ❎ - Uvicorn + +👑 👜 👆 💪 🏃 **FastAPI** 🈸 🛰 💽 🎰 🔫 💽 📋 💖 **Uvicorn**. + +📤 3️⃣ 👑 🎛: + +* Uvicorn: ↕ 🎭 🔫 💽. +* Hypercorn: 🔫 💽 🔗 ⏮️ 🇺🇸🔍/2️⃣ & 🎻 👪 🎏 ⚒. +* 👸: 🔫 💽 🏗 ✳ 📻. + +## 💽 🎰 & 💽 📋 + +📤 🤪 ℹ 🔃 📛 ✔️ 🤯. 👶 + +🔤 "**💽**" 🛎 ⚙️ 🔗 👯‍♂️ 🛰/☁ 💻 (⚛ ⚖️ 🕹 🎰) & 📋 👈 🏃‍♂ 🔛 👈 🎰 (✅ Uvicorn). + +✔️ 👈 🤯 🕐❔ 👆 ✍ "💽" 🏢, ⚫️ 💪 🔗 1️⃣ 📚 2️⃣ 👜. + +🕐❔ 🔗 🛰 🎰, ⚫️ ⚠ 🤙 ⚫️ **💽**, ✋️ **🎰**, **💾** (🕹 🎰), **🕸**. 👈 🌐 🔗 🆎 🛰 🎰, 🛎 🏃‍♂ 💾, 🌐❔ 👆 🏃 📋. + +## ❎ 💽 📋 + +👆 💪 ❎ 🔫 🔗 💽 ⏮️: + +=== "Uvicorn" + + * Uvicorn, 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool. + +
+ + ```console + $ pip install "uvicorn[standard]" + + ---> 100% + ``` + +
+ + !!! tip + ❎ `standard`, Uvicorn 🔜 ❎ & ⚙️ 👍 ➕ 🔗. + + 👈 ✅ `uvloop`, ↕-🎭 💧-♻ `asyncio`, 👈 🚚 🦏 🛠️ 🎭 📈. + +=== "Hypercorn" + + * Hypercorn, 🔫 💽 🔗 ⏮️ 🇺🇸🔍/2️⃣. + +
+ + ```console + $ pip install hypercorn + + ---> 100% + ``` + +
+ + ...⚖️ 🙆 🎏 🔫 💽. + +## 🏃 💽 📋 + +👆 💪 ⤴️ 🏃 👆 🈸 🎏 🌌 👆 ✔️ ⌛ 🔰, ✋️ 🍵 `--reload` 🎛, ✅: + +=== "Uvicorn" + +
+ + ```console + $ uvicorn main:app --host 0.0.0.0 --port 80 + + INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) + ``` + +
+ +=== "Hypercorn" + +
+ + ```console + $ hypercorn main:app --bind 0.0.0.0:80 + + Running on 0.0.0.0:8080 over http (CTRL + C to quit) + ``` + +
+ +!!! warning + 💭 ❎ `--reload` 🎛 🚥 👆 ⚙️ ⚫️. + + `--reload` 🎛 🍴 🌅 🌅 ℹ, 🌅 ⚠, ♒️. + + ⚫️ ℹ 📚 ⏮️ **🛠️**, ✋️ 👆 **🚫🔜 🚫** ⚙️ ⚫️ **🏭**. + +## Hypercorn ⏮️ 🎻 + +💃 & **FastAPI** ⚓️ 🔛 AnyIO, ❔ ⚒ 👫 🔗 ⏮️ 👯‍♂️ 🐍 🐩 🗃 & 🎻. + +👐, Uvicorn ⏳ 🕴 🔗 ⏮️ ✳, & ⚫️ 🛎 ⚙️ `uvloop`, ↕-🎭 💧-♻ `asyncio`. + +✋️ 🚥 👆 💚 🔗 ⚙️ **🎻**, ⤴️ 👆 💪 ⚙️ **Hypercorn** ⚫️ 🐕‍🦺 ⚫️. 👶 + +### ❎ Hypercorn ⏮️ 🎻 + +🥇 👆 💪 ❎ Hypercorn ⏮️ 🎻 🐕‍🦺: + +
+ +```console +$ pip install "hypercorn[trio]" +---> 100% +``` + +
+ +### 🏃 ⏮️ 🎻 + +⤴️ 👆 💪 🚶‍♀️ 📋 ⏸ 🎛 `--worker-class` ⏮️ 💲 `trio`: + +
+ +```console +$ hypercorn main:app --worker-class trio +``` + +
+ +& 👈 🔜 ▶️ Hypercorn ⏮️ 👆 📱 ⚙️ 🎻 👩‍💻. + +🔜 👆 💪 ⚙️ 🎻 🔘 👆 📱. ⚖️ 👍, 👆 💪 ⚙️ AnyIO, 🚧 👆 📟 🔗 ⏮️ 👯‍♂️ 🎻 & ✳. 👶 + +## 🛠️ 🔧 + +👫 🖼 🏃 💽 📋 (📧.Ⓜ Uvicorn), ▶️ **👁 🛠️**, 👂 🔛 🌐 📢 (`0.0.0.0`) 🔛 🔁 ⛴ (✅ `80`). + +👉 🔰 💭. ✋️ 👆 🔜 🎲 💚 ✊ 💅 🌖 👜, 💖: + +* 💂‍♂ - 🇺🇸🔍 +* 🏃‍♂ 🔛 🕴 +* ⏏ +* 🧬 (🔢 🛠️ 🏃) +* 💾 +* ⏮️ 🔁 ⏭ ▶️ + +👤 🔜 💬 👆 🌅 🔃 🔠 👫 🔧, ❔ 💭 🔃 👫, & 🧱 🖼 ⏮️ 🎛 🍵 👫 ⏭ 📃. 👶 diff --git a/docs/em/docs/deployment/server-workers.md b/docs/em/docs/deployment/server-workers.md new file mode 100644 index 0000000000..ca068d7447 --- /dev/null +++ b/docs/em/docs/deployment/server-workers.md @@ -0,0 +1,178 @@ +# 💽 👨‍🏭 - 🐁 ⏮️ Uvicorn + +➡️ ✅ 🔙 👈 🛠️ 🔧 ⚪️➡️ ⏭: + +* 💂‍♂ - 🇺🇸🔍 +* 🏃‍♂ 🔛 🕴 +* ⏏ +* **🧬 (🔢 🛠️ 🏃)** +* 💾 +* ⏮️ 🔁 ⏭ ▶️ + +🆙 👉 ☝, ⏮️ 🌐 🔰 🩺, 👆 ✔️ 🎲 🏃‍♂ **💽 📋** 💖 Uvicorn, 🏃‍♂ **👁 🛠️**. + +🕐❔ 🛠️ 🈸 👆 🔜 🎲 💚 ✔️ **🧬 🛠️** ✊ 📈 **💗 🐚** & 💪 🍵 🌅 📨. + +👆 👀 ⏮️ 📃 🔃 [🛠️ 🔧](./concepts.md){.internal-link target=_blank}, 📤 💗 🎛 👆 💪 ⚙️. + +📥 👤 🔜 🎦 👆 ❔ ⚙️ **🐁** ⏮️ **Uvicorn 👨‍🏭 🛠️**. + +!!! info + 🚥 👆 ⚙️ 📦, 🖼 ⏮️ ☁ ⚖️ Kubernete, 👤 🔜 💬 👆 🌅 🔃 👈 ⏭ 📃: [FastAPI 📦 - ☁](./docker.md){.internal-link target=_blank}. + + 🎯, 🕐❔ 🏃 🔛 **Kubernete** 👆 🔜 🎲 **🚫** 💚 ⚙️ 🐁 & ↩️ 🏃 **👁 Uvicorn 🛠️ 📍 📦**, ✋️ 👤 🔜 💬 👆 🔃 ⚫️ ⏪ 👈 📃. + +## 🐁 ⏮️ Uvicorn 👨‍🏭 + +**🐁** ✴️ 🈸 💽 ⚙️ **🇨🇻 🐩**. 👈 ⛓ 👈 🐁 💪 🍦 🈸 💖 🏺 & ✳. 🐁 ⚫️ 🚫 🔗 ⏮️ **FastAPI**, FastAPI ⚙️ 🆕 **🔫 🐩**. + +✋️ 🐁 🐕‍🦺 👷 **🛠️ 👨‍💼** & 🤝 👩‍💻 💬 ⚫️ ❔ 🎯 **👨‍🏭 🛠️ 🎓** ⚙️. ⤴️ 🐁 🔜 ▶️ 1️⃣ ⚖️ 🌖 **👨‍🏭 🛠️** ⚙️ 👈 🎓. + +& **Uvicorn** ✔️ **🐁-🔗 👨‍🏭 🎓**. + +⚙️ 👈 🌀, 🐁 🔜 🚫 **🛠️ 👨‍💼**, 👂 🔛 **⛴** & **📢**. & ⚫️ 🔜 **📶** 📻 👨‍🏭 🛠️ 🏃 **Uvicorn 🎓**. + +& ⤴️ 🐁-🔗 **Uvicorn 👨‍🏭** 🎓 🔜 🈚 🏭 📊 📨 🐁 🔫 🐩 FastAPI ⚙️ ⚫️. + +## ❎ 🐁 & Uvicorn + +
+ +```console +$ pip install "uvicorn[standard]" gunicorn + +---> 100% +``` + +
+ +👈 🔜 ❎ 👯‍♂️ Uvicorn ⏮️ `standard` ➕ 📦 (🤚 ↕ 🎭) & 🐁. + +## 🏃 🐁 ⏮️ Uvicorn 👨‍🏭 + +⤴️ 👆 💪 🏃 🐁 ⏮️: + +
+ +```console +$ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80 + +[19499] [INFO] Starting gunicorn 20.1.0 +[19499] [INFO] Listening at: http://0.0.0.0:80 (19499) +[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker +[19511] [INFO] Booting worker with pid: 19511 +[19513] [INFO] Booting worker with pid: 19513 +[19514] [INFO] Booting worker with pid: 19514 +[19515] [INFO] Booting worker with pid: 19515 +[19511] [INFO] Started server process [19511] +[19511] [INFO] Waiting for application startup. +[19511] [INFO] Application startup complete. +[19513] [INFO] Started server process [19513] +[19513] [INFO] Waiting for application startup. +[19513] [INFO] Application startup complete. +[19514] [INFO] Started server process [19514] +[19514] [INFO] Waiting for application startup. +[19514] [INFO] Application startup complete. +[19515] [INFO] Started server process [19515] +[19515] [INFO] Waiting for application startup. +[19515] [INFO] Application startup complete. +``` + +
+ +➡️ 👀 ⚫️❔ 🔠 👈 🎛 ⛓: + +* `main:app`: 👉 🎏 ❕ ⚙️ Uvicorn, `main` ⛓ 🐍 🕹 📛 "`main`",, 📁 `main.py`. & `app` 📛 🔢 👈 **FastAPI** 🈸. + * 👆 💪 🌈 👈 `main:app` 🌓 🐍 `import` 📄 💖: + + ```Python + from main import app + ``` + + * , ❤ `main:app` 🔜 🌓 🐍 `import` 🍕 `from main import app`. +* `--workers`: 🔢 👨‍🏭 🛠️ ⚙️, 🔠 🔜 🏃 Uvicorn 👨‍🏭, 👉 💼, 4️⃣ 👨‍🏭. +* `--worker-class`: 🐁-🔗 👨‍🏭 🎓 ⚙️ 👨‍🏭 🛠️. + * 📥 👥 🚶‍♀️ 🎓 👈 🐁 💪 🗄 & ⚙️ ⏮️: + + ```Python + import uvicorn.workers.UvicornWorker + ``` + +* `--bind`: 👉 💬 🐁 📢 & ⛴ 👂, ⚙️ ❤ (`:`) 🎏 📢 & ⛴. + * 🚥 👆 🏃‍♂ Uvicorn 🔗, ↩️ `--bind 0.0.0.0:80` (🐁 🎛) 👆 🔜 ⚙️ `--host 0.0.0.0` & `--port 80`. + +🔢, 👆 💪 👀 👈 ⚫️ 🎦 **🕹** (🛠️ 🆔) 🔠 🛠️ (⚫️ 🔢). + +👆 💪 👀 👈: + +* 🐁 **🛠️ 👨‍💼** ▶️ ⏮️ 🕹 `19499` (👆 💼 ⚫️ 🔜 🎏 🔢). +* ⤴️ ⚫️ ▶️ `Listening at: http://0.0.0.0:80`. +* ⤴️ ⚫️ 🔍 👈 ⚫️ ✔️ ⚙️ 👨‍🏭 🎓 `uvicorn.workers.UvicornWorker`. +* & ⤴️ ⚫️ ▶️ **4️⃣ 👨‍🏭**, 🔠 ⏮️ 🚮 👍 🕹: `19511`, `19513`, `19514`, & `19515`. + +🐁 🔜 ✊ 💅 🛠️ **☠️ 🛠️** & **🔁** 🆕 🕐 🚥 💚 🚧 🔢 👨‍🏭. 👈 ℹ 🍕 ⏮️ **⏏** 🔧 ⚪️➡️ 📇 🔛. + +👐, 👆 🔜 🎲 💚 ✔️ 🕳 🏞 ⚒ 💭 **⏏ 🐁** 🚥 💪, & **🏃 ⚫️ 🔛 🕴**, ♒️. + +## Uvicorn ⏮️ 👨‍🏭 + +Uvicorn ✔️ 🎛 ▶️ & 🏃 📚 **👨‍🏭 🛠️**. + +👐, 🔜, Uvicorn 🛠️ 🚚 👨‍🏭 🛠️ 🌅 📉 🌘 🐁. , 🚥 👆 💚 ✔️ 🛠️ 👨‍💼 👉 🎚 (🐍 🎚), ⤴️ ⚫️ 💪 👍 🔄 ⏮️ 🐁 🛠️ 👨‍💼. + +🙆 💼, 👆 🔜 🏃 ⚫️ 💖 👉: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +🕴 🆕 🎛 📥 `--workers` 💬 Uvicorn ▶️ 4️⃣ 👨‍🏭 🛠️. + +👆 💪 👀 👈 ⚫️ 🎦 **🕹** 🔠 🛠️, `27365` 👪 🛠️ (👉 **🛠️ 👨‍💼**) & 1️⃣ 🔠 👨‍🏭 🛠️: `27368`, `27369`, `27370`, & `27367`. + +## 🛠️ 🔧 + +📥 👆 👀 ❔ ⚙️ **🐁** (⚖️ Uvicorn) 🛠️ **Uvicorn 👨‍🏭 🛠️** **🔁** 🛠️ 🈸, ✊ 📈 **💗 🐚** 💽, & 💪 🍦 **🌅 📨**. + +⚪️➡️ 📇 🛠️ 🔧 ⚪️➡️ 🔛, ⚙️ 👨‍🏭 🔜 ✴️ ℹ ⏮️ **🧬** 🍕, & 🐥 🍖 ⏮️ **⏏**, ✋️ 👆 💪 ✊ 💅 🎏: + +* **💂‍♂ - 🇺🇸🔍** +* **🏃‍♂ 🔛 🕴** +* ***⏏*** +* 🧬 (🔢 🛠️ 🏃) +* **💾** +* **⏮️ 🔁 ⏭ ▶️** + +## 📦 & ☁ + +⏭ 📃 🔃 [FastAPI 📦 - ☁](./docker.md){.internal-link target=_blank} 👤 🔜 💬 🎛 👆 💪 ⚙️ 🍵 🎏 **🛠️ 🔧**. + +👤 🔜 🎦 👆 **🛂 ☁ 🖼** 👈 🔌 **🐁 ⏮️ Uvicorn 👨‍🏭** & 🔢 📳 👈 💪 ⚠ 🙅 💼. + +📤 👤 🔜 🎦 👆 ❔ **🏗 👆 👍 🖼 ⚪️➡️ 🖌** 🏃 👁 Uvicorn 🛠️ (🍵 🐁). ⚫️ 🙅 🛠️ & 🎲 ⚫️❔ 👆 🔜 💚 🕐❔ ⚙️ 📎 📦 🧾 ⚙️ 💖 **Kubernete**. + +## 🌃 + +👆 💪 ⚙️ **🐁** (⚖️ Uvicorn) 🛠️ 👨‍💼 ⏮️ Uvicorn 👨‍🏭 ✊ 📈 **👁-🐚 💽**, 🏃 **💗 🛠️ 🔗**. + +👆 💪 ⚙️ 👉 🧰 & 💭 🚥 👆 ⚒ 🆙 **👆 👍 🛠️ ⚙️** ⏪ ✊ 💅 🎏 🛠️ 🔧 👆. + +✅ 👅 ⏭ 📃 💡 🔃 **FastAPI** ⏮️ 📦 (✅ ☁ & Kubernete). 👆 🔜 👀 👈 👈 🧰 ✔️ 🙅 🌌 ❎ 🎏 **🛠️ 🔧** 👍. 👶 diff --git a/docs/em/docs/deployment/versions.md b/docs/em/docs/deployment/versions.md new file mode 100644 index 0000000000..8bfdf97318 --- /dev/null +++ b/docs/em/docs/deployment/versions.md @@ -0,0 +1,87 @@ +# 🔃 FastAPI ⏬ + +**FastAPI** ⏪ ➖ ⚙️ 🏭 📚 🈸 & ⚙️. & 💯 💰 🚧 1️⃣0️⃣0️⃣ 💯. ✋️ 🚮 🛠️ 🚚 🔜. + +🆕 ⚒ 🚮 🛎, 🐛 🔧 🛎, & 📟 🔁 📉. + +👈 ⚫️❔ ⏮️ ⏬ `0.x.x`, 👉 🎨 👈 🔠 ⏬ 💪 ⚠ ✔️ 💔 🔀. 👉 ⏩ ⚛ 🛠️ 🏛. + +👆 💪 ✍ 🏭 🈸 ⏮️ **FastAPI** ▶️️ 🔜 (& 👆 ✔️ 🎲 🔨 ⚫️ 🕰), 👆 ✔️ ⚒ 💭 👈 👆 ⚙️ ⏬ 👈 👷 ☑ ⏮️ 🎂 👆 📟. + +## 📌 👆 `fastapi` ⏬ + +🥇 👜 👆 🔜 "📌" ⏬ **FastAPI** 👆 ⚙️ 🎯 📰 ⏬ 👈 👆 💭 👷 ☑ 👆 🈸. + +🖼, ➡️ 💬 👆 ⚙️ ⏬ `0.45.0` 👆 📱. + +🚥 👆 ⚙️ `requirements.txt` 📁 👆 💪 ✔ ⏬ ⏮️: + +```txt +fastapi==0.45.0 +``` + +👈 🔜 ⛓ 👈 👆 🔜 ⚙️ ⚫️❔ ⏬ `0.45.0`. + +⚖️ 👆 💪 📌 ⚫️ ⏮️: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +👈 🔜 ⛓ 👈 👆 🔜 ⚙️ ⏬ `0.45.0` ⚖️ 🔛, ✋️ 🌘 🌘 `0.46.0`, 🖼, ⏬ `0.45.2` 🔜 🚫. + +🚥 👆 ⚙️ 🙆 🎏 🧰 🛠️ 👆 👷‍♂, 💖 🎶, Pipenv, ⚖️ 🎏, 👫 🌐 ✔️ 🌌 👈 👆 💪 ⚙️ 🔬 🎯 ⏬ 👆 📦. + +## 💪 ⏬ + +👆 💪 👀 💪 ⏬ (✅ ✅ ⚫️❔ ⏮️ 📰) [🚀 🗒](../release-notes.md){.internal-link target=_blank}. + +## 🔃 ⏬ + +📄 ⚛ 🛠️ 🏛, 🙆 ⏬ 🔛 `1.0.0` 💪 ⚠ 🚮 💔 🔀. + +FastAPI ⏩ 🏛 👈 🙆 "🐛" ⏬ 🔀 🐛 🔧 & 🚫-💔 🔀. + +!!! tip + "🐛" 🏁 🔢, 🖼, `0.2.3`, 🐛 ⏬ `3`. + +, 👆 🔜 💪 📌 ⏬ 💖: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +💔 🔀 & 🆕 ⚒ 🚮 "🇺🇲" ⏬. + +!!! tip + "🇺🇲" 🔢 🖕, 🖼, `0.2.3`, 🇺🇲 ⏬ `2`. + +## ♻ FastAPI ⏬ + +👆 🔜 🚮 💯 👆 📱. + +⏮️ **FastAPI** ⚫️ 📶 ⏩ (👏 💃), ✅ 🩺: [🔬](../tutorial/testing.md){.internal-link target=_blank} + +⏮️ 👆 ✔️ 💯, ⤴️ 👆 💪 ♻ **FastAPI** ⏬ 🌖 ⏮️ 1️⃣, & ⚒ 💭 👈 🌐 👆 📟 👷 ☑ 🏃 👆 💯. + +🚥 🌐 👷, ⚖️ ⏮️ 👆 ⚒ 💪 🔀, & 🌐 👆 💯 🚶‍♀️, ⤴️ 👆 💪 📌 👆 `fastapi` 👈 🆕 ⏮️ ⏬. + +## 🔃 💃 + +👆 🚫🔜 🚫 📌 ⏬ `starlette`. + +🎏 ⏬ **FastAPI** 🔜 ⚙️ 🎯 🆕 ⏬ 💃. + +, 👆 💪 ➡️ **FastAPI** ⚙️ ☑ 💃 ⏬. + +## 🔃 Pydantic + +Pydantic 🔌 💯 **FastAPI** ⏮️ 🚮 👍 💯, 🆕 ⏬ Pydantic (🔛 `1.0.0`) 🕧 🔗 ⏮️ FastAPI. + +👆 💪 📌 Pydantic 🙆 ⏬ 🔛 `1.0.0` 👈 👷 👆 & 🔛 `2.0.0`. + +🖼: + +```txt +pydantic>=1.2.0,<2.0.0 +``` diff --git a/docs/em/docs/external-links.md b/docs/em/docs/external-links.md new file mode 100644 index 0000000000..4440b1f122 --- /dev/null +++ b/docs/em/docs/external-links.md @@ -0,0 +1,91 @@ +# 🔢 🔗 & 📄 + +**FastAPI** ✔️ 👑 👪 🕧 💗. + +📤 📚 🏤, 📄, 🧰, & 🏗, 🔗 **FastAPI**. + +📥 ❌ 📇 👫. + +!!! tip + 🚥 👆 ✔️ 📄, 🏗, 🧰, ⚖️ 🕳 🔗 **FastAPI** 👈 🚫 📇 📥, ✍ 🚲 📨 ❎ ⚫️. + +## 📄 + +### 🇪🇸 + +{% if external_links %} +{% for article in external_links.articles.english %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +### 🇯🇵 + +{% if external_links %} +{% for article in external_links.articles.japanese %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +### 🇻🇳 + +{% if external_links %} +{% for article in external_links.articles.vietnamese %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +### 🇷🇺 + +{% if external_links %} +{% for article in external_links.articles.russian %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +### 🇩🇪 + +{% if external_links %} +{% for article in external_links.articles.german %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +### 🇹🇼 + +{% if external_links %} +{% for article in external_links.articles.taiwanese %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +## 📻 + +{% if external_links %} +{% for article in external_links.podcasts.english %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +## 💬 + +{% if external_links %} +{% for article in external_links.talks.english %} + +* {{ article.title }} {{ article.author }}. +{% endfor %} +{% endif %} + +## 🏗 + +⏪ 📂 🏗 ⏮️ ❔ `fastapi`: + +
+
diff --git a/docs/em/docs/fastapi-people.md b/docs/em/docs/fastapi-people.md new file mode 100644 index 0000000000..dc94d80da7 --- /dev/null +++ b/docs/em/docs/fastapi-people.md @@ -0,0 +1,178 @@ +# FastAPI 👫👫 + +FastAPI ✔️ 🎆 👪 👈 🙋 👫👫 ⚪️➡️ 🌐 🖥. + +## 👼 - 🐛 + +🙋 ❗ 👶 + +👉 👤: + +{% if people %} +
+{% for user in people.maintainers %} + +
@{{ user.login }}
❔: {{ user.answers }}
🚲 📨: {{ user.prs }}
+{% endfor %} + +
+{% endif %} + +👤 👼 & 🐛 **FastAPI**. 👆 💪 ✍ 🌅 🔃 👈 [ℹ FastAPI - 🤚 ℹ - 🔗 ⏮️ 📕](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}. + +...✋️ 📥 👤 💚 🎦 👆 👪. + +--- + +**FastAPI** 📨 📚 🐕‍🦺 ⚪️➡️ 👪. & 👤 💚 🎦 👫 💰. + +👫 👫👫 👈: + +* [ℹ 🎏 ⏮️ ❔ 📂](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank}. +* [✍ 🚲 📨](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}. +* 📄 🚲 📨, [✴️ ⚠ ✍](contributing.md#translations){.internal-link target=_blank}. + +👏 👫. 👶 👶 + +## 🌅 🦁 👩‍💻 🏁 🗓️ + +👫 👩‍💻 👈 ✔️ [🤝 🎏 🏆 ⏮️ ❔ 📂](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} ⏮️ 🏁 🗓️. 👶 + +{% if people %} +
+{% for user in people.last_month_active %} + +
@{{ user.login }}
❔ 📨: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## 🕴 + +📥 **FastAPI 🕴**. 👶 + +👫 👩‍💻 👈 ✔️ [ℹ 🎏 🏆 ⏮️ ❔ 📂](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} 🔘 *🌐 🕰*. + +👫 ✔️ 🎦 🕴 🤝 📚 🎏. 👶 + +{% if people %} +
+{% for user in people.experts %} + +
@{{ user.login }}
❔ 📨: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## 🔝 👨‍🔬 + +📥 **🔝 👨‍🔬**. 👶 + +👉 👩‍💻 ✔️ [✍ 🏆 🚲 📨](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} 👈 ✔️ *🔗*. + +👫 ✔️ 📉 ℹ 📟, 🧾, ✍, ♒️. 👶 + +{% if people %} +
+{% for user in people.top_contributors %} + +
@{{ user.login }}
🚲 📨: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +📤 📚 🎏 👨‍🔬 (🌅 🌘 💯), 👆 💪 👀 👫 🌐 FastAPI 📂 👨‍🔬 📃. 👶 + +## 🔝 👨‍🔬 + +👫 👩‍💻 **🔝 👨‍🔬**. 👶 👶 + +### 📄 ✍ + +👤 🕴 💬 👩‍❤‍👨 🇪🇸 (& 🚫 📶 👍 👶). , 👨‍🔬 🕐 👈 ✔️ [**🏋️ ✔ ✍**](contributing.md#translations){.internal-link target=_blank} 🧾. 🍵 👫, 📤 🚫🔜 🧾 📚 🎏 🇪🇸. + +--- + +**🔝 👨‍🔬** 👶 👶 ✔️ 📄 🏆 🚲 📨 ⚪️➡️ 🎏, 🚚 🔆 📟, 🧾, & ✴️, **✍**. + +{% if people %} +
+{% for user in people.top_reviewers %} + +
@{{ user.login }}
📄: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## 💰 + +👫 **💰**. 👶 + +👫 🔗 👇 👷 ⏮️ **FastAPI** (& 🎏), ✴️ 🔘 📂 💰. + +{% if sponsors %} + +{% if sponsors.gold %} + +### 🌟 💰 + +{% for sponsor in sponsors.gold -%} + +{% endfor %} +{% endif %} + +{% if sponsors.silver %} + +### 🥇1st 💰 + +{% for sponsor in sponsors.silver -%} + +{% endfor %} +{% endif %} + +{% if sponsors.bronze %} + +### 🥈2nd 💰 + +{% for sponsor in sponsors.bronze -%} + +{% endfor %} +{% endif %} + +{% endif %} + +### 🎯 💰 + +{% if github_sponsors %} +{% for group in github_sponsors.sponsors %} + +
+ +{% for user in group %} +{% if user.login not in sponsors_badge.logins %} + + + +{% endif %} +{% endfor %} + +
+ +{% endfor %} +{% endif %} + +## 🔃 📊 - 📡 ℹ + +👑 🎯 👉 📃 🎦 🎯 👪 ℹ 🎏. + +✴️ ✅ 🎯 👈 🛎 🌘 ⭐, & 📚 💼 🌅 😩, 💖 🤝 🎏 ⏮️ ❔ & ⚖ 🚲 📨 ⏮️ ✍. + +💽 ⚖ 🔠 🗓️, 👆 💪 ✍ ℹ 📟 📥. + +📥 👤 🎦 💰 ⚪️➡️ 💰. + +👤 🏦 ▶️️ ℹ 📊, 📄, ⚡, ♒️ (💼 🤷). diff --git a/docs/em/docs/features.md b/docs/em/docs/features.md new file mode 100644 index 0000000000..19193da075 --- /dev/null +++ b/docs/em/docs/features.md @@ -0,0 +1,200 @@ +# ⚒ + +## FastAPI ⚒ + +**FastAPI** 🤝 👆 📄: + +### ⚓️ 🔛 📂 🐩 + +* 🗄 🛠️ 🏗, ✅ 📄 🛠️, 🔢, 💪 📨, 💂‍♂, ♒️. +* 🏧 📊 🏷 🧾 ⏮️ 🎻 🔗 (🗄 ⚫️ 🧢 🔛 🎻 🔗). +* 🔧 🤭 👫 🐩, ⏮️ 😔 🔬. ↩️ 👎 🧽 🔛 🔝. +* 👉 ✔ ⚙️ 🏧 **👩‍💻 📟 ⚡** 📚 🇪🇸. + +### 🏧 🩺 + +🎓 🛠️ 🧾 & 🔬 🕸 👩‍💻 🔢. 🛠️ ⚓️ 🔛 🗄, 📤 💗 🎛, 2️⃣ 🔌 🔢. + +* 🦁 🎚, ⏮️ 🎓 🔬, 🤙 & 💯 👆 🛠️ 🔗 ⚪️➡️ 🖥. + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* 🎛 🛠️ 🧾 ⏮️ 📄. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### 🏛 🐍 + +⚫️ 🌐 ⚓️ 🔛 🐩 **🐍 3️⃣.6️⃣ 🆎** 📄 (👏 Pydantic). 🙅‍♂ 🆕 ❕ 💡. 🐩 🏛 🐍. + +🚥 👆 💪 2️⃣ ⏲ ↗️ ❔ ⚙️ 🐍 🆎 (🚥 👆 🚫 ⚙️ FastAPI), ✅ 📏 🔰: [🐍 🆎](python-types.md){.internal-link target=_blank}. + +👆 ✍ 🐩 🐍 ⏮️ 🆎: + +```Python +from datetime import date + +from pydantic import BaseModel + +# Declare a variable as a str +# and get editor support inside the function +def main(user_id: str): + return user_id + + +# A Pydantic model +class User(BaseModel): + id: int + name: str + joined: date +``` + +👈 💪 ⤴️ ⚙️ 💖: + +```Python +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +!!! info + `**second_user_data` ⛓: + + 🚶‍♀️ 🔑 & 💲 `second_user_data` #️⃣ 🔗 🔑-💲 ❌, 🌓: `User(id=4, name="Mary", joined="2018-11-30")` + +### 👨‍🎨 🐕‍🦺 + +🌐 🛠️ 🏗 ⏩ & 🏋️ ⚙️, 🌐 🚫 💯 🔛 💗 👨‍🎨 ⏭ ▶️ 🛠️, 🚚 🏆 🛠️ 💡. + +🏁 🐍 👩‍💻 🔬 ⚫️ 🆑 👈 🌅 ⚙️ ⚒ "✍". + +🎂 **FastAPI** 🛠️ ⚓️ 😌 👈. ✍ 👷 🌐. + +👆 🔜 🛎 💪 👟 🔙 🩺. + +📥 ❔ 👆 👨‍🎨 💪 ℹ 👆: + +* 🎙 🎙 📟: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +* 🗒: + +![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) + +👆 🔜 🤚 🛠️ 📟 👆 5️⃣📆 🤔 💪 ⏭. 🖼, `price` 🔑 🔘 🎻 💪 (👈 💪 ✔️ 🐦) 👈 👟 ⚪️➡️ 📨. + +🙅‍♂ 🌖 ⌨ ❌ 🔑 📛, 👟 🔙 & ➡ 🖖 🩺, ⚖️ 📜 🆙 & 🔽 🔎 🚥 👆 😒 ⚙️ `username` ⚖️ `user_name`. + +### 📏 + +⚫️ ✔️ 🤔 **🔢** 🌐, ⏮️ 📦 📳 🌐. 🌐 🔢 💪 👌-🎧 ⚫️❔ 👆 💪 & 🔬 🛠️ 👆 💪. + +✋️ 🔢, ⚫️ 🌐 **"👷"**. + +### 🔬 + +* 🔬 🌅 (⚖️ 🌐 ❓) 🐍 **💽 🆎**, 🔌: + * 🎻 🎚 (`dict`). + * 🎻 🎻 (`list`) ⚖ 🏬 🆎. + * 🎻 (`str`) 🏑, 🔬 🕙 & 👟 📐. + * 🔢 (`int`, `float`) ⏮️ 🕙 & 👟 💲, ♒️. + +* 🔬 🌅 😍 🆎, 💖: + * 📛. + * 📧. + * 🆔. + * ...& 🎏. + +🌐 🔬 🍵 👍-🏛 & 🏋️ **Pydantic**. + +### 💂‍♂ & 🤝 + +💂‍♂ & 🤝 🛠️. 🍵 🙆 ⚠ ⏮️ 💽 ⚖️ 📊 🏷. + +🌐 💂‍♂ ⚖ 🔬 🗄, 🔌: + +* 🇺🇸🔍 🔰. +* **Oauth2️⃣** (⏮️ **🥙 🤝**). ✅ 🔰 🔛 [Oauth2️⃣ ⏮️ 🥙](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* 🛠️ 🔑: + * 🎚. + * 🔢 🔢. + * 🍪, ♒️. + +➕ 🌐 💂‍♂ ⚒ ⚪️➡️ 💃 (🔌 **🎉 🍪**). + +🌐 🏗 ♻ 🧰 & 🦲 👈 ⏩ 🛠️ ⏮️ 👆 ⚙️, 📊 🏪, 🔗 & ☁ 💽, ♒️. + +### 🔗 💉 + +FastAPI 🔌 📶 ⏩ ⚙️, ✋️ 📶 🏋️ 🔗 💉 ⚙️. + +* 🔗 💪 ✔️ 🔗, 🏗 🔗 ⚖️ **"📊" 🔗**. +* 🌐 **🔁 🍵** 🛠️. +* 🌐 🔗 💪 🚚 💽 ⚪️➡️ 📨 & **↔ ➡ 🛠️** ⚛ & 🏧 🧾. +* **🏧 🔬** *➡ 🛠️* 🔢 🔬 🔗. +* 🐕‍🦺 🏗 👩‍💻 🤝 ⚙️, **💽 🔗**, ♒️. +* **🙅‍♂ ⚠** ⏮️ 💽, 🕸, ♒️. ✋️ ⏩ 🛠️ ⏮️ 🌐 👫. + +### ♾ "🔌-🔌" + +⚖️ 🎏 🌌, 🙅‍♂ 💪 👫, 🗄 & ⚙️ 📟 👆 💪. + +🙆 🛠️ 🏗 🙅 ⚙️ (⏮️ 🔗) 👈 👆 💪 ✍ "🔌-" 👆 🈸 2️⃣ ⏸ 📟 ⚙️ 🎏 📊 & ❕ ⚙️ 👆 *➡ 🛠️*. + +### 💯 + +* 1️⃣0️⃣0️⃣ 💯 💯 💰. +* 1️⃣0️⃣0️⃣ 💯 🆎 ✍ 📟 🧢. +* ⚙️ 🏭 🈸. + +## 💃 ⚒ + +**FastAPI** 🍕 🔗 ⏮️ (& ⚓️ 🔛) 💃. , 🙆 🌖 💃 📟 👆 ✔️, 🔜 👷. + +`FastAPI` 🤙 🎧-🎓 `Starlette`. , 🚥 👆 ⏪ 💭 ⚖️ ⚙️ 💃, 🌅 🛠️ 🔜 👷 🎏 🌌. + +⏮️ **FastAPI** 👆 🤚 🌐 **💃**'Ⓜ ⚒ (FastAPI 💃 🔛 💊): + +* 🤙 🎆 🎭. ⚫️ 1️⃣ ⏩ 🐍 🛠️ 💪, 🔛 🇷🇪 ⏮️ **✳** & **🚶**. +* ** *️⃣ ** 🐕‍🦺. +* -🛠️ 🖥 📋. +* 🕴 & 🤫 🎉. +* 💯 👩‍💻 🏗 🔛 🇸🇲. +* **⚜**, 🗜, 🎻 📁, 🎏 📨. +* **🎉 & 🍪** 🐕‍🦺. +* 1️⃣0️⃣0️⃣ 💯 💯 💰. +* 1️⃣0️⃣0️⃣ 💯 🆎 ✍ ✍. + +## Pydantic ⚒ + +**FastAPI** 🍕 🔗 ⏮️ (& ⚓️ 🔛) Pydantic. , 🙆 🌖 Pydantic 📟 👆 ✔️, 🔜 👷. + +✅ 🔢 🗃 ⚓️ 🔛 Pydantic, 🐜Ⓜ, 🏭Ⓜ 💽. + +👉 ⛓ 👈 📚 💼 👆 💪 🚶‍♀️ 🎏 🎚 👆 🤚 ⚪️➡️ 📨 **🔗 💽**, 🌐 ✔ 🔁. + +🎏 ✔ 🎏 🌌 🤭, 📚 💼 👆 💪 🚶‍♀️ 🎚 👆 🤚 ⚪️➡️ 💽 **🔗 👩‍💻**. + +⏮️ **FastAPI** 👆 🤚 🌐 **Pydantic**'Ⓜ ⚒ (FastAPI ⚓️ 🔛 Pydantic 🌐 💽 🚚): + +* **🙅‍♂ 🔠**: + * 🙅‍♂ 🆕 🔗 🔑 ◾-🇪🇸 💡. + * 🚥 👆 💭 🐍 🆎 👆 💭 ❔ ⚙️ Pydantic. +* 🤾 🎆 ⏮️ 👆 **💾/🧶/🧠**: + * ↩️ Pydantic 📊 📊 👐 🎓 👆 🔬; 🚘-🛠️, 🧽, ✍ & 👆 🤔 🔜 🌐 👷 ☑ ⏮️ 👆 ✔ 💽. +* **⏩**: + * 📇 Pydantic ⏩ 🌘 🌐 🎏 💯 🗃. +* ✔ **🏗 📊**: + * ⚙️ 🔗 Pydantic 🏷, 🐍 `typing`'Ⓜ `List` & `Dict`, ♒️. + * & 💳 ✔ 🏗 💽 🔗 🎯 & 💪 🔬, ✅ & 📄 🎻 🔗. + * 👆 💪 ✔️ 🙇 **🐦 🎻** 🎚 & ✔️ 👫 🌐 ✔ & ✍. +* **🏧**: + * Pydantic ✔ 🛃 📊 🆎 🔬 ⚖️ 👆 💪 ↔ 🔬 ⏮️ 👩‍🔬 🔛 🏷 🎀 ⏮️ 💳 👨‍🎨. +* 1️⃣0️⃣0️⃣ 💯 💯 💰. diff --git a/docs/em/docs/help-fastapi.md b/docs/em/docs/help-fastapi.md new file mode 100644 index 0000000000..d7b66185d4 --- /dev/null +++ b/docs/em/docs/help-fastapi.md @@ -0,0 +1,265 @@ +# ℹ FastAPI - 🤚 ℹ + +👆 💖 **FastAPI**❓ + +🔜 👆 💖 ℹ FastAPI, 🎏 👩‍💻, & 📕 ❓ + +⚖️ 🔜 👆 💖 🤚 ℹ ⏮️ **FastAPI**❓ + +📤 📶 🙅 🌌 ℹ (📚 🔌 1️⃣ ⚖️ 2️⃣ 🖊). + +& 📤 📚 🌌 🤚 ℹ 💁‍♂️. + +## 👱📔 📰 + +👆 💪 👱📔 (🐌) [**FastAPI & 👨‍👧‍👦** 📰](/newsletter/){.internal-link target=_blank} 🚧 ℹ 🔃: + +* 📰 🔃 FastAPI & 👨‍👧‍👦 👶 +* 🦮 👶 +* ⚒ 👶 +* 💔 🔀 👶 +* 💁‍♂ & 🎱 👶 + +## ⏩ FastAPI 🔛 👱📔 + +⏩ 🐶 Fastapi 🔛 **👱📔** 🤚 📰 📰 🔃 **FastAPI**. 👶 + +## ✴ **FastAPI** 📂 + +👆 💪 "✴" FastAPI 📂 (🖊 ✴ 🔼 🔝 ▶️️): https://github.com/tiangolo/fastapi. 👶 👶 + +❎ ✴, 🎏 👩‍💻 🔜 💪 🔎 ⚫️ 🌅 💪 & 👀 👈 ⚫️ ✔️ ⏪ ⚠ 🎏. + +## ⌚ 📂 🗃 🚀 + +👆 💪 "⌚" FastAPI 📂 (🖊 "⌚" 🔼 🔝 ▶️️): https://github.com/tiangolo/fastapi. 👶 + +📤 👆 💪 🖊 "🚀 🕴". + +🔨 ⚫️, 👆 🔜 📨 📨 (👆 📧) 🕐❔ 📤 🆕 🚀 (🆕 ⏬) **FastAPI** ⏮️ 🐛 🔧 & 🆕 ⚒. + +## 🔗 ⏮️ 📕 + +👆 💪 🔗 ⏮️ 👤 (🇹🇦 🇩🇬 / `tiangolo`), 📕. + +👆 💪: + +* ⏩ 👤 🔛 **📂**. + * 👀 🎏 📂 ℹ 🏗 👤 ✔️ ✍ 👈 💪 ℹ 👆. + * ⏩ 👤 👀 🕐❔ 👤 ✍ 🆕 📂 ℹ 🏗. +* ⏩ 👤 🔛 **👱📔** ⚖️ . + * 💬 👤 ❔ 👆 ⚙️ FastAPI (👤 💌 👂 👈). + * 👂 🕐❔ 👤 ⚒ 🎉 ⚖️ 🚀 🆕 🧰. + * 👆 💪 ⏩ 🐶 Fastapi 🔛 👱📔 (🎏 🏧). +* 🔗 ⏮️ 👤 🔛 **👱📔**. + * 👂 🕐❔ 👤 ⚒ 🎉 ⚖️ 🚀 🆕 🧰 (👐 👤 ⚙️ 👱📔 🌖 🛎 🤷 ♂). +* ✍ ⚫️❔ 👤 ✍ (⚖️ ⏩ 👤) 🔛 **🇸🇲.** ⚖️ **🔉**. + * ✍ 🎏 💭, 📄, & ✍ 🔃 🧰 👤 ✔️ ✍. + * ⏩ 👤 ✍ 🕐❔ 👤 ✍ 🕳 🆕. + +## 👱📔 🔃 **FastAPI** + +👱📔 🔃 **FastAPI** & ➡️ 👤 & 🎏 💭 ⚫️❔ 👆 💖 ⚫️. 👶 + +👤 💌 👂 🔃 ❔ **FastAPI** 💆‍♂ ⚙️, ⚫️❔ 👆 ✔️ 💖 ⚫️, ❔ 🏗/🏢 👆 ⚙️ ⚫️, ♒️. + +## 🗳 FastAPI + +* 🗳 **FastAPI** 📐. +* 🗳 **FastAPI** 📱. +* 💬 👆 ⚙️ **FastAPI** 🔛 ℹ. + +## ℹ 🎏 ⏮️ ❔ 📂 + +👆 💪 🔄 & ℹ 🎏 ⏮️ 👫 ❔: + +* 📂 💬 +* 📂 ❔ + +📚 💼 👆 5️⃣📆 ⏪ 💭 ❔ 📚 ❔. 👶 + +🚥 👆 🤝 📚 👫👫 ⏮️ 👫 ❔, 👆 🔜 ▶️️ 🛂 [FastAPI 🕴](fastapi-people.md#experts){.internal-link target=_blank}. 👶 + +💭, 🏆 ⚠ ☝: 🔄 😇. 👫👫 👟 ⏮️ 👫 😩 & 📚 💼 🚫 💭 🏆 🌌, ✋️ 🔄 🏆 👆 💪 😇. 👶 + +💭 **FastAPI** 👪 😇 & 👍. 🎏 🕰, 🚫 🚫 🎭 ⚖️ 😛 🎭 ⤵ 🎏. 👥 ✔️ ✊ 💅 🔠 🎏. + +--- + +📥 ❔ ℹ 🎏 ⏮️ ❔ (💬 ⚖️ ❔): + +### 🤔 ❔ + +* ✅ 🚥 👆 💪 🤔 ⚫️❔ **🎯** & ⚙️ 💼 👨‍💼 💬. + +* ⤴️ ✅ 🚥 ❔ (⭕ 👪 ❔) **🆑**. + +* 📚 💼 ❔ 💭 🔃 👽 ⚗ ⚪️➡️ 👩‍💻, ✋️ 📤 💪 **👍** 1️⃣. 🚥 👆 💪 🤔 ⚠ & ⚙️ 💼 👍, 👆 💪 💪 🤔 👍 **🎛 ⚗**. + +* 🚥 👆 💪 🚫 🤔 ❔, 💭 🌖 **ℹ**. + +### 🔬 ⚠ + +🌅 💼 & 🏆 ❔ 📤 🕳 🔗 👨‍💼 **⏮️ 📟**. + +📚 💼 👫 🔜 🕴 📁 🧬 📟, ✋️ 👈 🚫 🥃 **🔬 ⚠**. + +* 👆 💪 💭 👫 🚚 ⭐, 🔬, 🖼, 👈 👆 💪 **📁-📋** & 🏃 🌐 👀 🎏 ❌ ⚖️ 🎭 👫 👀, ⚖️ 🤔 👫 ⚙️ 💼 👍. + +* 🚥 👆 😟 💁‍♂️ 👍, 👆 💪 🔄 **✍ 🖼** 💖 👈 👆, 🧢 🔛 📛 ⚠. ✔️ 🤯 👈 👉 💪 ✊ 📚 🕰 & ⚫️ 💪 👻 💭 👫 ✍ ⚠ 🥇. + +### 🤔 ⚗ + +* ⏮️ 💆‍♂ 💪 🤔 ❔, 👆 💪 🤝 👫 💪 **❔**. + +* 📚 💼, ⚫️ 👍 🤔 👫 **📈 ⚠ ⚖️ ⚙️ 💼**, ↩️ 📤 5️⃣📆 👍 🌌 ❎ ⚫️ 🌘 ⚫️❔ 👫 🔄. + +### 💭 🔐 + +🚥 👫 📨, 📤 ↕ 🤞 👆 🔜 ✔️ ❎ 👫 ⚠, ㊗, **👆 💂**❗ 🦸 + +* 🔜, 🚥 👈 ❎ 👫 ⚠, 👆 💪 💭 👫: + + * 📂 💬: ™ 🏤 **❔**. + * 📂 ❔: **🔐** ❔**. + +## ⌚ 📂 🗃 + +👆 💪 "⌚" FastAPI 📂 (🖊 "⌚" 🔼 🔝 ▶️️): https://github.com/tiangolo/fastapi. 👶 + +🚥 👆 🖊 "👀" ↩️ "🚀 🕴" 👆 🔜 📨 📨 🕐❔ 👱 ✍ 🆕 ❔ ⚖️ ❔. 👆 💪 ✔ 👈 👆 🕴 💚 🚨 🔃 🆕 ❔, ⚖️ 💬, ⚖️ 🎸, ♒️. + +⤴️ 👆 💪 🔄 & ℹ 👫 ❎ 👈 ❔. + +## 💭 ❔ + +👆 💪 ✍ 🆕 ❔ 📂 🗃, 🖼: + +* 💭 **❔** ⚖️ 💭 🔃 **⚠**. +* 🤔 🆕 **⚒**. + +**🗒**: 🚥 👆 ⚫️, ⤴️ 👤 🔜 💭 👆 ℹ 🎏. 👶 + +## 📄 🚲 📨 + +👆 💪 ℹ 👤 📄 🚲 📨 ⚪️➡️ 🎏. + +🔄, 🙏 🔄 👆 🏆 😇. 👶 + +--- + +📥 ⚫️❔ ✔️ 🤯 & ❔ 📄 🚲 📨: + +### 🤔 ⚠ + +* 🥇, ⚒ 💭 👆 **🤔 ⚠** 👈 🚲 📨 🔄 ❎. ⚫️ 💪 ✔️ 📏 💬 📂 💬 ⚖️ ❔. + +* 📤 👍 🤞 👈 🚲 📨 🚫 🤙 💪 ↩️ ⚠ 💪 ❎ **🎏 🌌**. ⤴️ 👆 💪 🤔 ⚖️ 💭 🔃 👈. + +### 🚫 😟 🔃 👗 + +* 🚫 😟 💁‍♂️ 🌅 🔃 👜 💖 💕 📧 👗, 👤 🔜 🥬 & 🔗 🛃 💕 ❎. + +* 🚫 😟 🔃 👗 🚫, 📤 ⏪ 🏧 🧰 ✅ 👈. + +& 🚥 📤 🙆 🎏 👗 ⚖️ ⚖ 💪, 👤 🔜 💭 🔗 👈, ⚖️ 👤 🔜 🚮 💕 🔛 🔝 ⏮️ 💪 🔀. + +### ✅ 📟 + +* ✅ & ✍ 📟, 👀 🚥 ⚫️ ⚒ 🔑, **🏃 ⚫️ 🌐** & 👀 🚥 ⚫️ 🤙 ❎ ⚠. + +* ⤴️ **🏤** 💬 👈 👆 👈, 👈 ❔ 👤 🔜 💭 👆 🤙 ✅ ⚫️. + +!!! info + 👐, 👤 💪 🚫 🎯 💙 🎸 👈 ✔️ 📚 ✔. + + 📚 🕰 ⚫️ ✔️ 🔨 👈 📤 🎸 ⏮️ 3️⃣, 5️⃣ ⚖️ 🌅 ✔, 🎲 ↩️ 📛 😌, ✋️ 🕐❔ 👤 ✅ 🎸, 👫 🤙 💔, ✔️ 🐛, ⚖️ 🚫 ❎ ⚠ 👫 🛄 ❎. 👶 + + , ⚫️ 🤙 ⚠ 👈 👆 🤙 ✍ & 🏃 📟, & ➡️ 👤 💭 🏤 👈 👆. 👶 + +* 🚥 🇵🇷 💪 📉 🌌, 👆 💪 💭 👈, ✋️ 📤 🙅‍♂ 💪 💁‍♂️ 😟, 📤 5️⃣📆 📚 🤔 ☝ 🎑 (& 👤 🔜 ✔️ 👇 👍 👍 👶), ⚫️ 👻 🚥 👆 💪 🎯 🔛 ⚛ 👜. + +### 💯 + +* ℹ 👤 ✅ 👈 🇵🇷 ✔️ **💯**. + +* ✅ 👈 💯 **❌** ⏭ 🇵🇷. 👶 + +* ⤴️ ✅ 👈 💯 **🚶‍♀️** ⏮️ 🇵🇷. 👶 + +* 📚 🎸 🚫 ✔️ 💯, 👆 💪 **🎗** 👫 🚮 💯, ⚖️ 👆 💪 **🤔** 💯 👆. 👈 1️⃣ 👜 👈 🍴 🌅 🕰 & 👆 💪 ℹ 📚 ⏮️ 👈. + +* ⤴️ 🏤 ⚫️❔ 👆 🔄, 👈 🌌 👤 🔜 💭 👈 👆 ✅ ⚫️. 👶 + +## ✍ 🚲 📨 + +👆 💪 [📉](contributing.md){.internal-link target=_blank} ℹ 📟 ⏮️ 🚲 📨, 🖼: + +* 🔧 🤭 👆 🔎 🔛 🧾. +* 💰 📄, 📹, ⚖️ 📻 👆 ✍ ⚖️ 🔎 🔃 FastAPI ✍ 👉 📁. + * ⚒ 💭 👆 🚮 👆 🔗 ▶️ 🔗 📄. +* ℹ [💬 🧾](contributing.md#translations){.internal-link target=_blank} 👆 🇪🇸. + * 👆 💪 ℹ 📄 ✍ ✍ 🎏. +* 🛠️ 🆕 🧾 📄. +* 🔧 ♻ ❔/🐛. + * ⚒ 💭 🚮 💯. +* 🚮 🆕 ⚒. + * ⚒ 💭 🚮 💯. + * ⚒ 💭 🚮 🧾 🚥 ⚫️ 🔗. + +## ℹ 🚧 FastAPI + +ℹ 👤 🚧 **FastAPI**❗ 👶 + +📤 📚 👷, & 🏆 ⚫️, **👆** 💪 ⚫️. + +👑 📋 👈 👆 💪 ▶️️ 🔜: + +* [ℹ 🎏 ⏮️ ❔ 📂](#help-others-with-questions-in-github){.internal-link target=_blank} (👀 📄 🔛). +* [📄 🚲 📨](#review-pull-requests){.internal-link target=_blank} (👀 📄 🔛). + +👈 2️⃣ 📋 ⚫️❔ **🍴 🕰 🏆**. 👈 👑 👷 🏆 FastAPI. + +🚥 👆 💪 ℹ 👤 ⏮️ 👈, **👆 🤝 👤 🚧 FastAPI** & ⚒ 💭 ⚫️ 🚧 **🛠️ ⏩ & 👻**. 👶 + +## 🛑 💬 + +🛑 👶 😧 💬 💽 👶 & 🤙 👅 ⏮️ 🎏 FastAPI 👪. + +!!! tip + ❔, 💭 👫 📂 💬, 📤 🌅 👍 🤞 👆 🔜 📨 ℹ [FastAPI 🕴](fastapi-people.md#experts){.internal-link target=_blank}. + + ⚙️ 💬 🕴 🎏 🏢 💬. + +📤 ⏮️ 🥊 💬, ✋️ ⚫️ 🚫 ✔️ 📻 & 🏧 ⚒, 💬 🌖 ⚠, 😧 🔜 👍 ⚙️. + +### 🚫 ⚙️ 💬 ❔ + +✔️ 🤯 👈 💬 ✔ 🌅 "🆓 💬", ⚫️ ⏩ 💭 ❔ 👈 💁‍♂️ 🏢 & 🌅 ⚠ ❔,, 👆 💪 🚫 📨 ❔. + +📂, 📄 🔜 🦮 👆 ✍ ▶️️ ❔ 👈 👆 💪 🌖 💪 🤚 👍 ❔, ⚖️ ❎ ⚠ 👆 ⏭ 💬. & 📂 👤 💪 ⚒ 💭 👤 🕧 ❔ 🌐, 🚥 ⚫️ ✊ 🕰. 👤 💪 🚫 🤙 👈 ⏮️ 💬 ⚙️. 👶 + +💬 💬 ⚙️ 🚫 💪 📇 📂, ❔ & ❔ 5️⃣📆 🤚 💸 💬. & 🕴 🕐 📂 💯 ▶️️ [FastAPI 🕴](fastapi-people.md#experts){.internal-link target=_blank}, 👆 🔜 🌅 🎲 📨 🌅 🙋 📂. + +🔛 🎏 🚄, 📤 💯 👩‍💻 💬 ⚙️, 📤 ↕ 🤞 👆 🔜 🔎 👱 💬 📤, 🌖 🌐 🕰. 👶 + +## 💰 📕 + +👆 💪 💰 🐕‍🦺 📕 (👤) 🔘 📂 💰. + +📤 👆 💪 🛍 👤 ☕ 👶 👶 💬 👏. 👶 + +& 👆 💪 ▶️️ 🥇1st ⚖️ 🌟 💰 FastAPI. 👶 👶 + +## 💰 🧰 👈 🏋️ FastAPI + +👆 ✔️ 👀 🧾, FastAPI 🧍 🔛 ⌚ 🐘, 💃 & Pydantic. + +👆 💪 💰: + +* ✡ 🍏 (Pydantic) +* 🗜 (💃, Uvicorn) + +--- + +👏 ❗ 👶 diff --git a/docs/em/docs/history-design-future.md b/docs/em/docs/history-design-future.md new file mode 100644 index 0000000000..7e39972de8 --- /dev/null +++ b/docs/em/docs/history-design-future.md @@ -0,0 +1,79 @@ +# 📖, 🔧 & 🔮 + +🕰 🏁, **FastAPI** 👩‍💻 💭: + +> ⚫️❔ 📖 👉 🏗 ❓ ⚫️ 😑 ✔️ 👟 ⚪️➡️ 🕳 👌 👩‍❤‍👨 🗓️ [...] + +📥 🐥 🍖 👈 📖. + +## 🎛 + +👤 ✔️ 🏗 🔗 ⏮️ 🏗 📄 📚 1️⃣2️⃣🗓️ (🎰 🏫, 📎 ⚙️, 🔁 👨‍🏭, ☁ 💽, ♒️), ↘️ 📚 🏉 👩‍💻. + +🍕 👈, 👤 💪 🔬, 💯 & ⚙️ 📚 🎛. + +📖 **FastAPI** 👑 🍕 📖 🚮 ⏪. + +🙆‍♀ 📄 [🎛](alternatives.md){.internal-link target=_blank}: + +
+ +**FastAPI** 🚫🔜 🔀 🚥 🚫 ⏮️ 👷 🎏. + +📤 ✔️ 📚 🧰 ✍ ⏭ 👈 ✔️ ℹ 😮 🚮 🏗. + +👤 ✔️ ❎ 🏗 🆕 🛠️ 📚 1️⃣2️⃣🗓️. 🥇 👤 🔄 ❎ 🌐 ⚒ 📔 **FastAPI** ⚙️ 📚 🎏 🛠️, 🔌-🔌, & 🧰. + +✋️ ☝, 📤 🙅‍♂ 🎏 🎛 🌘 🏗 🕳 👈 🚚 🌐 👫 ⚒, ✊ 🏆 💭 ⚪️➡️ ⏮️ 🧰, & 🌀 👫 🏆 🌌 💪, ⚙️ 🇪🇸 ⚒ 👈 ➖🚫 💪 ⏭ (🐍 3️⃣.6️⃣ ➕ 🆎 🔑). + +
+ +## 🔬 + +⚙️ 🌐 ⏮️ 🎛 👤 ✔️ 🤞 💡 ⚪️➡️ 🌐 👫, ✊ 💭, & 🌀 👫 🏆 🌌 👤 💪 🔎 👤 & 🏉 👩‍💻 👤 ✔️ 👷 ⏮️. + +🖼, ⚫️ 🆑 👈 🎲 ⚫️ 🔜 ⚓️ 🔛 🐩 🐍 🆎 🔑. + +, 🏆 🎯 ⚙️ ⏪ ♻ 🐩. + +, ⏭ ▶️ 📟 **FastAPI**, 👤 💸 📚 🗓️ 🎓 🔌 🗄, 🎻 🔗, Oauth2️⃣, ♒️. 🎯 👫 💛, 🔀, & 🔺. + +## 🔧 + +⤴️ 👤 💸 🕰 🔧 👩‍💻 "🛠️" 👤 💚 ✔️ 👩‍💻 (👩‍💻 ⚙️ FastAPI). + +👤 💯 📚 💭 🏆 🌟 🐍 👨‍🎨: 🗒, 🆚 📟, 🎠 🧢 👨‍🎨. + +🏁 🐍 👩‍💻 🔬, 👈 📔 🔃 8️⃣0️⃣ 💯 👩‍💻. + +⚫️ ⛓ 👈 **FastAPI** 🎯 💯 ⏮️ 👨‍🎨 ⚙️ 8️⃣0️⃣ 💯 🐍 👩‍💻. & 🏆 🎏 👨‍🎨 😑 👷 ➡, 🌐 🚮 💰 🔜 👷 🌖 🌐 👨‍🎨. + +👈 🌌 👤 💪 🔎 🏆 🌌 📉 📟 ❎ 🌅 💪, ✔️ 🛠️ 🌐, 🆎 & ❌ ✅, ♒️. + +🌐 🌌 👈 🚚 🏆 🛠️ 💡 🌐 👩‍💻. + +## 📄 + +⏮️ 🔬 📚 🎛, 👤 💭 👈 👤 🔜 ⚙️ **Pydantic** 🚮 📈. + +⤴️ 👤 📉 ⚫️, ⚒ ⚫️ 🍕 🛠️ ⏮️ 🎻 🔗, 🐕‍🦺 🎏 🌌 🔬 ⚛ 📄, & 📉 👨‍🎨 🐕‍🦺 (🆎 ✅, ✍) ⚓️ 🔛 💯 📚 👨‍🎨. + +⏮️ 🛠️, 👤 📉 **💃**, 🎏 🔑 📄. + +## 🛠️ + +🕰 👤 ▶️ 🏗 **FastAPI** ⚫️, 🏆 🍖 ⏪ 🥉, 🔧 🔬, 📄 & 🧰 🔜, & 💡 🔃 🐩 & 🔧 🆑 & 🍋. + +## 🔮 + +👉 ☝, ⚫️ ⏪ 🆑 👈 **FastAPI** ⏮️ 🚮 💭 ➖ ⚠ 📚 👫👫. + +⚫️ 💆‍♂ 👐 🤭 ⏮️ 🎛 ♣ 📚 ⚙️ 💼 👍. + +📚 👩‍💻 & 🏉 ⏪ 🪀 🔛 **FastAPI** 👫 🏗 (🔌 👤 & 👇 🏉). + +✋️, 📤 📚 📈 & ⚒ 👟. + +**FastAPI** ✔️ 👑 🔮 ⤴️. + +& [👆 ℹ](help-fastapi.md){.internal-link target=_blank} 📉 👍. diff --git a/docs/em/docs/index.md b/docs/em/docs/index.md new file mode 100644 index 0000000000..ea8a9d41c8 --- /dev/null +++ b/docs/em/docs/index.md @@ -0,0 +1,469 @@ +

+ FastAPI +

+

+ FastAPI 🛠️, ↕ 🎭, ⏩ 💡, ⏩ 📟, 🔜 🏭 +

+

+ + Test + + + Coverage + + + Package version + + + Supported Python versions + +

+ +--- + +**🧾**: https://fastapi.tiangolo.com + +**ℹ 📟**: https://github.com/tiangolo/fastapi + +--- + +FastAPI 🏛, ⏩ (↕-🎭), 🕸 🛠️ 🏗 🛠️ ⏮️ 🐍 3️⃣.7️⃣ ➕ ⚓️ 🔛 🐩 🐍 🆎 🔑. + +🔑 ⚒: + +* **⏩**: 📶 ↕ 🎭, 🔛 🇷🇪 ⏮️ **✳** & **🚶** (👏 💃 & Pydantic). [1️⃣ ⏩ 🐍 🛠️ 💪](#performance). +* **⏩ 📟**: 📈 🚅 🛠️ ⚒ 🔃 2️⃣0️⃣0️⃣ 💯 3️⃣0️⃣0️⃣ 💯. * +* **👩‍❤‍👨 🐛**: 📉 🔃 4️⃣0️⃣ 💯 🗿 (👩‍💻) 📉 ❌. * +* **🏋️**: 👑 👨‍🎨 🐕‍🦺. 🛠️ 🌐. 🌘 🕰 🛠️. +* **⏩**: 🔧 ⏩ ⚙️ & 💡. 🌘 🕰 👂 🩺. +* **📏**: 📉 📟 ❎. 💗 ⚒ ⚪️➡️ 🔠 🔢 📄. 👩‍❤‍👨 🐛. +* **🏋️**: 🤚 🏭-🔜 📟. ⏮️ 🏧 🎓 🧾. +* **🐩-⚓️**: ⚓️ 🔛 (& 🍕 🔗 ⏮️) 📂 🐩 🔗: 🗄 (⏪ 💭 🦁) & 🎻 🔗. + +* ⚖ ⚓️ 🔛 💯 🔛 🔗 🛠️ 🏉, 🏗 🏭 🈸. + +## 💰 + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} + +{% endfor -%} +{%- for sponsor in sponsors.silver -%} + +{% endfor %} +{% endif %} + + + +🎏 💰 + +## 🤔 + +"_[...] 👤 ⚙️ **FastAPI** 📚 👫 📆. [...] 👤 🤙 📆 ⚙️ ⚫️ 🌐 👇 🏉 **⚗ 🐕‍🦺 🤸‍♂**. 👫 💆‍♂ 🛠️ 🔘 🐚 **🖥** 🏬 & **📠** 🏬._" + +
🧿 🇵🇰 - 🤸‍♂ (🇦🇪)
+ +--- + +"_👥 🛠️ **FastAPI** 🗃 🤖 **🎂** 💽 👈 💪 🔢 🚚 **🔮**. [👨📛]_" + +
🇮🇹 🇸🇻, 👨📛 👨📛, & 🇱🇰 🕉 🕉 - 🙃 (🇦🇪)
+ +--- + +"_**📺** 🙏 📣 📂-ℹ 🚀 👆 **⚔ 🧾** 🎶 🛠️: **📨**❗ [🏗 ⏮️ **FastAPI**]_" + +
✡ 🍏, 👖 🇪🇸, 🌲 🍏 - 📺 (🇦🇪)
+ +--- + +"_👤 🤭 🌕 😄 🔃 **FastAPI**. ⚫️ 🎊 ❗_" + +
✡ 🇭🇰 - 🐍 🔢 📻 🦠 (🇦🇪)
+ +--- + +"_🤙, ⚫️❔ 👆 ✔️ 🏗 👀 💎 💠 & 🇵🇱. 📚 🌌, ⚫️ ⚫️❔ 👤 💚 **🤗** - ⚫️ 🤙 😍 👀 👱 🏗 👈._" + +
✡ 🗄 - 🤗 👼 (🇦🇪)
+ +--- + +"_🚥 👆 👀 💡 1️⃣ **🏛 🛠️** 🏗 🎂 🔗, ✅ 👅 **FastAPI** [...] ⚫️ ⏩, ⏩ ⚙️ & ⏩ 💡 [...]_" + +"_👥 ✔️ 🎛 🤭 **FastAPI** 👆 **🔗** [...] 👤 💭 👆 🔜 💖 ⚫️ [...]_" + +
🇱🇨 🇸🇲 - ✡ Honnibal - 💥 👲 🕴 - 🌈 👼 (🇦🇪) - (🇦🇪)
+ +--- + +"_🚥 🙆 👀 🏗 🏭 🐍 🛠️, 👤 🔜 🏆 👍 **FastAPI**. ⚫️ **💎 🏗**, **🙅 ⚙️** & **🏆 🛠️**, ⚫️ ✔️ ▶️️ **🔑 🦲** 👆 🛠️ 🥇 🛠️ 🎛 & 🚘 📚 🏧 & 🐕‍🦺 ✅ 👆 🕹 🔫 👨‍💻._" + +
🇹🇦 🍰 - 📻 (🇦🇪)
+ +--- + +## **🏎**, FastAPI 🇳🇨 + + + +🚥 👆 🏗 📱 ⚙️ 📶 ↩️ 🕸 🛠️, ✅ 👅 **🏎**. + +**🏎** FastAPI 🐥 👪. & ⚫️ 🎯 **FastAPI 🇳🇨**. 👶 👶 👶 + +## 📄 + +🐍 3️⃣.7️⃣ ➕ + +FastAPI 🧍 🔛 ⌚ 🐘: + +* 💃 🕸 🍕. +* Pydantic 📊 🍕. + +## 👷‍♂ + +
+ +```console +$ pip install fastapi + +---> 100% +``` + +
+ +👆 🔜 💪 🔫 💽, 🏭 ✅ Uvicorn ⚖️ Hypercorn. + +
+ +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +
+ +## 🖼 + +### ✍ ⚫️ + +* ✍ 📁 `main.py` ⏮️: + +```Python +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +
+⚖️ ⚙️ async def... + +🚥 👆 📟 ⚙️ `async` / `await`, ⚙️ `async def`: + +```Python hl_lines="9 14" +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +**🗒**: + +🚥 👆 🚫 💭, ✅ _"🏃 ❓" _ 📄 🔃 `async` & `await` 🩺. + +
+ +### 🏃 ⚫️ + +🏃 💽 ⏮️: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +
+🔃 📋 uvicorn main:app --reload... + +📋 `uvicorn main:app` 🔗: + +* `main`: 📁 `main.py` (🐍 "🕹"). +* `app`: 🎚 ✍ 🔘 `main.py` ⏮️ ⏸ `app = FastAPI()`. +* `--reload`: ⚒ 💽 ⏏ ⏮️ 📟 🔀. 🕴 👉 🛠️. + +
+ +### ✅ ⚫️ + +📂 👆 🖥 http://127.0.0.1:8000/items/5?q=somequery. + +👆 🔜 👀 🎻 📨: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +👆 ⏪ ✍ 🛠️ 👈: + +* 📨 🇺🇸🔍 📨 _➡_ `/` & `/items/{item_id}`. +* 👯‍♂️ _➡_ ✊ `GET` 🛠️ (💭 🇺🇸🔍 _👩‍🔬_). +* _➡_ `/items/{item_id}` ✔️ _➡ 🔢_ `item_id` 👈 🔜 `int`. +* _➡_ `/items/{item_id}` ✔️ 📦 `str` _🔢 = `q`. + +### 🎓 🛠️ 🩺 + +🔜 🚶 http://127.0.0.1:8000/docs. + +👆 🔜 👀 🏧 🎓 🛠️ 🧾 (🚚 🦁 🎚): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### 🎛 🛠️ 🩺 + +& 🔜, 🚶 http://127.0.0.1:8000/redoc. + +👆 🔜 👀 🎛 🏧 🧾 (🚚 📄): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## 🖼 ♻ + +🔜 🔀 📁 `main.py` 📨 💪 ⚪️➡️ `PUT` 📨. + +📣 💪 ⚙️ 🐩 🐍 🆎, 👏 Pydantic. + +```Python hl_lines="4 9-12 25-27" +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +💽 🔜 🔃 🔁 (↩️ 👆 🚮 `--reload` `uvicorn` 📋 🔛). + +### 🎓 🛠️ 🩺 ♻ + +🔜 🚶 http://127.0.0.1:8000/docs. + +* 🎓 🛠️ 🧾 🔜 🔁 ℹ, 🔌 🆕 💪: + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* 🖊 🔛 🔼 "🔄 ⚫️ 👅", ⚫️ ✔ 👆 🥧 🔢 & 🔗 🔗 ⏮️ 🛠️: + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) + +* ⤴️ 🖊 🔛 "🛠️" 🔼, 👩‍💻 🔢 🔜 🔗 ⏮️ 👆 🛠️, 📨 🔢, 🤚 🏁 & 🎦 👫 🔛 🖥: + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) + +### 🎛 🛠️ 🩺 ♻ + +& 🔜, 🚶 http://127.0.0.1:8000/redoc. + +* 🎛 🧾 🔜 🎨 🆕 🔢 🔢 & 💪: + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### 🌃 + +📄, 👆 📣 **🕐** 🆎 🔢, 💪, ♒️. 🔢 🔢. + +👆 👈 ⏮️ 🐩 🏛 🐍 🆎. + +👆 🚫 ✔️ 💡 🆕 ❕, 👩‍🔬 ⚖️ 🎓 🎯 🗃, ♒️. + +🐩 **🐍 3️⃣.7️⃣ ➕**. + +🖼, `int`: + +```Python +item_id: int +``` + +⚖️ 🌖 🏗 `Item` 🏷: + +```Python +item: Item +``` + +...& ⏮️ 👈 👁 📄 👆 🤚: + +* 👨‍🎨 🐕‍🦺, 🔌: + * 🛠️. + * 🆎 ✅. +* 🔬 💽: + * 🏧 & 🆑 ❌ 🕐❔ 📊 ❌. + * 🔬 🙇 🐦 🎻 🎚. +* 🛠️ 🔢 💽: 👟 ⚪️➡️ 🕸 🐍 💽 & 🆎. 👂 ⚪️➡️: + * 🎻. + * ➡ 🔢. + * 🔢 🔢. + * 🍪. + * 🎚. + * 📨. + * 📁. +* 🛠️ 🔢 📊: 🗜 ⚪️➡️ 🐍 💽 & 🆎 🕸 💽 (🎻): + * 🗜 🐍 🆎 (`str`, `int`, `float`, `bool`, `list`, ♒️). + * `datetime` 🎚. + * `UUID` 🎚. + * 💽 🏷. + * ...& 📚 🌖. +* 🏧 🎓 🛠️ 🧾, 🔌 2️⃣ 🎛 👩‍💻 🔢: + * 🦁 🎚. + * 📄. + +--- + +👟 🔙 ⏮️ 📟 🖼, **FastAPI** 🔜: + +* ✔ 👈 📤 `item_id` ➡ `GET` & `PUT` 📨. +* ✔ 👈 `item_id` 🆎 `int` `GET` & `PUT` 📨. + * 🚥 ⚫️ 🚫, 👩‍💻 🔜 👀 ⚠, 🆑 ❌. +* ✅ 🚥 📤 📦 🔢 🔢 📛 `q` ( `http://127.0.0.1:8000/items/foo?q=somequery`) `GET` 📨. + * `q` 🔢 📣 ⏮️ `= None`, ⚫️ 📦. + * 🍵 `None` ⚫️ 🔜 🚚 (💪 💼 ⏮️ `PUT`). +* `PUT` 📨 `/items/{item_id}`, ✍ 💪 🎻: + * ✅ 👈 ⚫️ ✔️ ✔ 🔢 `name` 👈 🔜 `str`. + * ✅ 👈 ⚫️ ✔️ ✔ 🔢 `price` 👈 ✔️ `float`. + * ✅ 👈 ⚫️ ✔️ 📦 🔢 `is_offer`, 👈 🔜 `bool`, 🚥 🎁. + * 🌐 👉 🔜 👷 🙇 🐦 🎻 🎚. +* 🗜 ⚪️➡️ & 🎻 🔁. +* 📄 🌐 ⏮️ 🗄, 👈 💪 ⚙️: + * 🎓 🧾 ⚙️. + * 🏧 👩‍💻 📟 ⚡ ⚙️, 📚 🇪🇸. +* 🚚 2️⃣ 🎓 🧾 🕸 🔢 🔗. + +--- + +👥 🖌 🧽, ✋️ 👆 ⏪ 🤚 💭 ❔ ⚫️ 🌐 👷. + +🔄 🔀 ⏸ ⏮️: + +```Python + return {"item_name": item.name, "item_id": item_id} +``` + +...⚪️➡️: + +```Python + ... "item_name": item.name ... +``` + +...: + +```Python + ... "item_price": item.price ... +``` + +...& 👀 ❔ 👆 👨‍🎨 🔜 🚘-🏁 🔢 & 💭 👫 🆎: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +🌅 🏁 🖼 🔌 🌅 ⚒, 👀 🔰 - 👩‍💻 🦮. + +**🚘 🚨**: 🔰 - 👩‍💻 🦮 🔌: + +* 📄 **🔢** ⚪️➡️ 🎏 🎏 🥉: **🎚**, **🍪**, **📨 🏑** & **📁**. +* ❔ ⚒ **🔬 ⚛** `maximum_length` ⚖️ `regex`. +* 📶 🏋️ & ⏩ ⚙️ **🔗 💉** ⚙️. +* 💂‍♂ & 🤝, ✅ 🐕‍🦺 **Oauth2️⃣** ⏮️ **🥙 🤝** & **🇺🇸🔍 🔰** 🔐. +* 🌅 🏧 (✋️ 😨 ⏩) ⚒ 📣 **🙇 🐦 🎻 🏷** (👏 Pydantic). +* **🕹** 🛠️ ⏮️ 🍓 & 🎏 🗃. +* 📚 ➕ ⚒ (👏 💃): + * ** *️⃣ ** + * 📶 ⏩ 💯 ⚓️ 🔛 🇸🇲 & `pytest` + * **⚜** + * **🍪 🎉** + * ...& 🌖. + +## 🎭 + +🔬 🇸🇲 📇 🎦 **FastAPI** 🈸 🏃‍♂ 🔽 Uvicorn 1️⃣ ⏩ 🐍 🛠️ 💪, 🕴 🔛 💃 & Uvicorn 👫 (⚙️ 🔘 FastAPI). (*) + +🤔 🌖 🔃 ⚫️, 👀 📄 📇. + +## 📦 🔗 + +⚙️ Pydantic: + +* ujson - ⏩ 🎻 "🎻". +* email_validator - 📧 🔬. + +⚙️ 💃: + +* httpx - ✔ 🚥 👆 💚 ⚙️ `TestClient`. +* jinja2 - ✔ 🚥 👆 💚 ⚙️ 🔢 📄 📳. +* python-multipart - ✔ 🚥 👆 💚 🐕‍🦺 📨 "✍", ⏮️ `request.form()`. +* itsdangerous - ✔ `SessionMiddleware` 🐕‍🦺. +* pyyaml - ✔ 💃 `SchemaGenerator` 🐕‍🦺 (👆 🎲 🚫 💪 ⚫️ ⏮️ FastAPI). +* ujson - ✔ 🚥 👆 💚 ⚙️ `UJSONResponse`. + +⚙️ FastAPI / 💃: + +* uvicorn - 💽 👈 📐 & 🍦 👆 🈸. +* orjson - ✔ 🚥 👆 💚 ⚙️ `ORJSONResponse`. + +👆 💪 ❎ 🌐 👫 ⏮️ `pip install "fastapi[all]"`. + +## 🛂 + +👉 🏗 ® 🔽 ⚖ 🇩🇪 🛂. diff --git a/docs/em/docs/project-generation.md b/docs/em/docs/project-generation.md new file mode 100644 index 0000000000..5fd667ad19 --- /dev/null +++ b/docs/em/docs/project-generation.md @@ -0,0 +1,84 @@ +# 🏗 ⚡ - 📄 + +👆 💪 ⚙️ 🏗 🚂 🤚 ▶️, ⚫️ 🔌 📚 ▶️ ⚒ 🆙, 💂‍♂, 💽 & 🛠️ 🔗 ⏪ ⌛ 👆. + +🏗 🚂 🔜 🕧 ✔️ 📶 🙃 🖥 👈 👆 🔜 ℹ & 🛠️ 👆 👍 💪, ✋️ ⚫️ 💪 👍 ▶️ ☝ 👆 🏗. + +## 🌕 📚 FastAPI ✳ + +📂: https://github.com/tiangolo/full-stack-fastapi-postgresql + +### 🌕 📚 FastAPI ✳ - ⚒ + +* 🌕 **☁** 🛠️ (☁ 🧢). +* ☁ 🐝 📳 🛠️. +* **☁ ✍** 🛠️ & 🛠️ 🇧🇿 🛠️. +* **🏭 🔜** 🐍 🕸 💽 ⚙️ Uvicorn & 🐁. +* 🐍 **FastAPI** 👩‍💻: + * **⏩**: 📶 ↕ 🎭, 🔛 🇷🇪 ⏮️ **✳** & **🚶** (👏 💃 & Pydantic). + * **🏋️**: 👑 👨‍🎨 🐕‍🦺. 🛠️ 🌐. 🌘 🕰 🛠️. + * **⏩**: 🔧 ⏩ ⚙️ & 💡. 🌘 🕰 👂 🩺. + * **📏**: 📉 📟 ❎. 💗 ⚒ ⚪️➡️ 🔠 🔢 📄. + * **🏋️**: 🤚 🏭-🔜 📟. ⏮️ 🏧 🎓 🧾. + * **🐩-⚓️**: ⚓️ 🔛 (& 🍕 🔗 ⏮️) 📂 🐩 🔗: 🗄 & 🎻 🔗. + * **📚 🎏 ⚒** 🔌 🏧 🔬, 🛠️, 🎓 🧾, 🤝 ⏮️ Oauth2️⃣ 🥙 🤝, ♒️. +* **🔐 🔐** 🔁 🔢. +* **🥙 🤝** 🤝. +* **🇸🇲** 🏷 (🔬 🏺 ↔, 👫 💪 ⚙️ ⏮️ 🥒 👨‍🏭 🔗). +* 🔰 ▶️ 🏷 👩‍💻 (🔀 & ❎ 👆 💪). +* **⚗** 🛠️. +* **⚜** (✖️ 🇨🇳 ℹ 🤝). +* **🥒** 👨‍🏭 👈 💪 🗄 & ⚙️ 🏷 & 📟 ⚪️➡️ 🎂 👩‍💻 🍕. +* 🎂 👩‍💻 💯 ⚓️ 🔛 **✳**, 🛠️ ⏮️ ☁, 👆 💪 💯 🌕 🛠️ 🔗, 🔬 🔛 💽. ⚫️ 🏃 ☁, ⚫️ 💪 🏗 🆕 💽 🏪 ⚪️➡️ 🖌 🔠 🕰 (👆 💪 ⚙️ ✳, ✳, ✳, ⚖️ ⚫️❔ 👆 💚, & 💯 👈 🛠️ 👷). +* ⏩ 🐍 🛠️ ⏮️ **📂 💾** 🛰 ⚖️-☁ 🛠️ ⏮️ ↔ 💖 ⚛ ⚗ ⚖️ 🎙 🎙 📟 📂. +* **🎦** 🕸: + * 🏗 ⏮️ 🎦 ✳. + * **🥙 🤝** 🚚. + * 💳 🎑. + * ⏮️ 💳, 👑 🕹 🎑. + * 👑 🕹 ⏮️ 👩‍💻 🏗 & 📕. + * 👤 👩‍💻 📕. + * **🇷🇪**. + * **🎦-📻**. + * **Vuetify** 🌹 🧽 🔧 🦲. + * **📕**. + * ☁ 💽 ⚓️ 🔛 **👌** (📶 🤾 🎆 ⏮️ 🎦-📻). + * ☁ 👁-▶️ 🏗, 👆 🚫 💪 🖊 ⚖️ 💕 ✍ 📟. + * 🕸 💯 🏃 🏗 🕰 (💪 🔕 💁‍♂️). + * ⚒ 🔧 💪, ⚫️ 👷 👅 📦, ✋️ 👆 💪 🏤-🏗 ⏮️ 🎦 ✳ ⚖️ ✍ ⚫️ 👆 💪, & 🏤-⚙️ ⚫️❔ 👆 💚. +* ** *️⃣ ** ✳ 💽, 👆 💪 🔀 ⚫️ ⚙️ 📁 & ✳ 💪. +* **🥀** 🥒 👨‍🏭 ⚖. +* 📐 ⚖ 🖖 🕸 & 👩‍💻 ⏮️ **Traefik**, 👆 💪 ✔️ 👯‍♂️ 🔽 🎏 🆔, 👽 ➡, ✋️ 🍦 🎏 📦. +* Traefik 🛠️, ✅ ➡️ 🗜 **🇺🇸🔍** 📄 🏧 ⚡. +* ✳ **🆑** (🔁 🛠️), 🔌 🕸 & 👩‍💻 🔬. + +## 🌕 📚 FastAPI 🗄 + +📂: https://github.com/tiangolo/full-stack-fastapi-couchbase + +👶 👶 **⚠** 👶 👶 + +🚥 👆 ▶️ 🆕 🏗 ⚪️➡️ 🖌, ✅ 🎛 📥. + +🖼, 🏗 🚂 🌕 📚 FastAPI ✳ 💪 👍 🎛, ⚫️ 🎯 🚧 & ⚙️. & ⚫️ 🔌 🌐 🆕 ⚒ & 📈. + +👆 🆓 ⚙️ 🗄-⚓️ 🚂 🚥 👆 💚, ⚫️ 🔜 🎲 👷 👌, & 🚥 👆 ⏪ ✔️ 🏗 🏗 ⏮️ ⚫️ 👈 👌 👍 (& 👆 🎲 ⏪ ℹ ⚫️ ♣ 👆 💪). + +👆 💪 ✍ 🌅 🔃 ⚫️ 🩺 🏦. + +## 🌕 📚 FastAPI ✳ + +...💪 👟 ⏪, ⚓️ 🔛 👇 🕰 🚚 & 🎏 ⚖. 👶 👶 + +## 🎰 🏫 🏷 ⏮️ 🌈 & FastAPI + +📂: https://github.com/microsoft/cookiecutter-spacy-fastapi + +### 🎰 🏫 🏷 ⏮️ 🌈 & FastAPI - ⚒ + +* **🌈** 🕜 🏷 🛠️. +* **☁ 🧠 🔎** 📨 📁 🏗. +* **🏭 🔜** 🐍 🕸 💽 ⚙️ Uvicorn & 🐁. +* **☁ 👩‍💻** Kubernete (🦲) 🆑/💿 🛠️ 🏗. +* **🤸‍♂** 💪 ⚒ 1️⃣ 🌈 🏗 🇪🇸 ⏮️ 🏗 🖥. +* **💪 🏧** 🎏 🏷 🛠️ (Pytorch, 🇸🇲), 🚫 🌈. diff --git a/docs/em/docs/python-types.md b/docs/em/docs/python-types.md new file mode 100644 index 0000000000..e079d9039d --- /dev/null +++ b/docs/em/docs/python-types.md @@ -0,0 +1,490 @@ +# 🐍 🆎 🎶 + +🐍 ✔️ 🐕‍🦺 📦 "🆎 🔑". + +👫 **"🆎 🔑"** 🎁 ❕ 👈 ✔ 📣 🆎 🔢. + +📣 🆎 👆 🔢, 👨‍🎨 & 🧰 💪 🤝 👆 👍 🐕‍🦺. + +👉 **⏩ 🔰 / ↗️** 🔃 🐍 🆎 🔑. ⚫️ 📔 🕴 💯 💪 ⚙️ 👫 ⏮️ **FastAPI**... ❔ 🤙 📶 🐥. + +**FastAPI** 🌐 ⚓️ 🔛 👫 🆎 🔑, 👫 🤝 ⚫️ 📚 📈 & 💰. + +✋️ 🚥 👆 🙅 ⚙️ **FastAPI**, 👆 🔜 💰 ⚪️➡️ 🏫 🍖 🔃 👫. + +!!! note + 🚥 👆 🐍 🕴, & 👆 ⏪ 💭 🌐 🔃 🆎 🔑, 🚶 ⏭ 📃. + +## 🎯 + +➡️ ▶️ ⏮️ 🙅 🖼: + +```Python +{!../../../docs_src/python_types/tutorial001.py!} +``` + +🤙 👉 📋 🔢: + +``` +John Doe +``` + +🔢 🔨 📄: + +* ✊ `first_name` & `last_name`. +* 🗜 🥇 🔤 🔠 1️⃣ ↖ 💼 ⏮️ `title()`. +* 🔢 👫 ⏮️ 🚀 🖕. + +```Python hl_lines="2" +{!../../../docs_src/python_types/tutorial001.py!} +``` + +### ✍ ⚫️ + +⚫️ 📶 🙅 📋. + +✋️ 🔜 🌈 👈 👆 ✍ ⚫️ ⚪️➡️ 🖌. + +☝ 👆 🔜 ✔️ ▶️ 🔑 🔢, 👆 ✔️ 🔢 🔜... + +✋️ ⤴️ 👆 ✔️ 🤙 "👈 👩‍🔬 👈 🗜 🥇 🔤 ↖ 💼". + +⚫️ `upper`❓ ⚫️ `uppercase`❓ `first_uppercase`❓ `capitalize`❓ + +⤴️, 👆 🔄 ⏮️ 🗝 👩‍💻 👨‍👧‍👦, 👨‍🎨 ✍. + +👆 🆎 🥇 🔢 🔢, `first_name`, ⤴️ ❣ (`.`) & ⤴️ 🎯 `Ctrl+Space` ⏲ 🛠️. + +✋️, 😞, 👆 🤚 🕳 ⚠: + + + +### 🚮 🆎 + +➡️ 🔀 👁 ⏸ ⚪️➡️ ⏮️ ⏬. + +👥 🔜 🔀 ⚫️❔ 👉 🧬, 🔢 🔢, ⚪️➡️: + +```Python + first_name, last_name +``` + +: + +```Python + first_name: str, last_name: str +``` + +👈 ⚫️. + +👈 "🆎 🔑": + +```Python hl_lines="1" +{!../../../docs_src/python_types/tutorial002.py!} +``` + +👈 🚫 🎏 📣 🔢 💲 💖 🔜 ⏮️: + +```Python + first_name="john", last_name="doe" +``` + +⚫️ 🎏 👜. + +👥 ⚙️ ❤ (`:`), 🚫 🌓 (`=`). + +& ❎ 🆎 🔑 🛎 🚫 🔀 ⚫️❔ 🔨 ⚪️➡️ ⚫️❔ 🔜 🔨 🍵 👫. + +✋️ 🔜, 🌈 👆 🔄 🖕 🏗 👈 🔢, ✋️ ⏮️ 🆎 🔑. + +🎏 ☝, 👆 🔄 ⏲ 📋 ⏮️ `Ctrl+Space` & 👆 👀: + + + +⏮️ 👈, 👆 💪 📜, 👀 🎛, ⏭ 👆 🔎 1️⃣ 👈 "💍 🔔": + + + +## 🌅 🎯 + +✅ 👉 🔢, ⚫️ ⏪ ✔️ 🆎 🔑: + +```Python hl_lines="1" +{!../../../docs_src/python_types/tutorial003.py!} +``` + +↩️ 👨‍🎨 💭 🆎 🔢, 👆 🚫 🕴 🤚 🛠️, 👆 🤚 ❌ ✅: + + + +🔜 👆 💭 👈 👆 ✔️ 🔧 ⚫️, 🗜 `age` 🎻 ⏮️ `str(age)`: + +```Python hl_lines="2" +{!../../../docs_src/python_types/tutorial004.py!} +``` + +## 📣 🆎 + +👆 👀 👑 🥉 📣 🆎 🔑. 🔢 🔢. + +👉 👑 🥉 👆 🔜 ⚙️ 👫 ⏮️ **FastAPI**. + +### 🙅 🆎 + +👆 💪 📣 🌐 🐩 🐍 🆎, 🚫 🕴 `str`. + +👆 💪 ⚙️, 🖼: + +* `int` +* `float` +* `bool` +* `bytes` + +```Python hl_lines="1" +{!../../../docs_src/python_types/tutorial005.py!} +``` + +### 💊 🆎 ⏮️ 🆎 🔢 + +📤 📊 📊 👈 💪 🔌 🎏 💲, 💖 `dict`, `list`, `set` & `tuple`. & 🔗 💲 💪 ✔️ 👫 👍 🆎 💁‍♂️. + +👉 🆎 👈 ✔️ 🔗 🆎 🤙 "**💊**" 🆎. & ⚫️ 💪 📣 👫, ⏮️ 👫 🔗 🆎. + +📣 👈 🆎 & 🔗 🆎, 👆 💪 ⚙️ 🐩 🐍 🕹 `typing`. ⚫️ 🔀 🎯 🐕‍🦺 👫 🆎 🔑. + +#### 🆕 ⏬ 🐍 + +❕ ⚙️ `typing` **🔗** ⏮️ 🌐 ⏬, ⚪️➡️ 🐍 3️⃣.6️⃣ ⏪ 🕐, ✅ 🐍 3️⃣.9️⃣, 🐍 3️⃣.1️⃣0️⃣, ♒️. + +🐍 🏧, **🆕 ⏬** 👟 ⏮️ 📉 🐕‍🦺 👉 🆎 ✍ & 📚 💼 👆 🏆 🚫 💪 🗄 & ⚙️ `typing` 🕹 📣 🆎 ✍. + +🚥 👆 💪 ⚒ 🌖 ⏮️ ⏬ 🐍 👆 🏗, 👆 🔜 💪 ✊ 📈 👈 ➕ 🦁. 👀 🖼 🔛. + +#### 📇 + +🖼, ➡️ 🔬 🔢 `list` `str`. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ⚪️➡️ `typing`, 🗄 `List` (⏮️ 🔠 `L`): + + ``` Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial006.py!} + ``` + + 📣 🔢, ⏮️ 🎏 ❤ (`:`) ❕. + + 🆎, 🚮 `List` 👈 👆 🗄 ⚪️➡️ `typing`. + + 📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜: + + ```Python hl_lines="4" + {!> ../../../docs_src/python_types/tutorial006.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + 📣 🔢, ⏮️ 🎏 ❤ (`:`) ❕. + + 🆎, 🚮 `list`. + + 📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜: + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial006_py39.py!} + ``` + +!!! info + 👈 🔗 🆎 ⬜ 🗜 🤙 "🆎 🔢". + + 👉 💼, `str` 🆎 🔢 🚶‍♀️ `List` (⚖️ `list` 🐍 3️⃣.9️⃣ & 🔛). + +👈 ⛓: "🔢 `items` `list`, & 🔠 🏬 👉 📇 `str`". + +!!! tip + 🚥 👆 ⚙️ 🐍 3️⃣.9️⃣ ⚖️ 🔛, 👆 🚫 ✔️ 🗄 `List` ⚪️➡️ `typing`, 👆 💪 ⚙️ 🎏 🥔 `list` 🆎 ↩️. + +🔨 👈, 👆 👨‍🎨 💪 🚚 🐕‍🦺 ⏪ 🏭 🏬 ⚪️➡️ 📇: + + + +🍵 🆎, 👈 🌖 💪 🏆. + +👀 👈 🔢 `item` 1️⃣ 🔣 📇 `items`. + +& , 👨‍🎨 💭 ⚫️ `str`, & 🚚 🐕‍🦺 👈. + +#### 🔢 & ⚒ + +👆 🔜 🎏 📣 `tuple`Ⓜ & `set`Ⓜ: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial007.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial007_py39.py!} + ``` + +👉 ⛓: + +* 🔢 `items_t` `tuple` ⏮️ 3️⃣ 🏬, `int`, ➕1️⃣ `int`, & `str`. +* 🔢 `items_s` `set`, & 🔠 🚮 🏬 🆎 `bytes`. + +#### #️⃣ + +🔬 `dict`, 👆 🚶‍♀️ 2️⃣ 🆎 🔢, 🎏 ❕. + +🥇 🆎 🔢 🔑 `dict`. + +🥈 🆎 🔢 💲 `dict`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial008_py39.py!} + ``` + +👉 ⛓: + +* 🔢 `prices` `dict`: + * 🔑 👉 `dict` 🆎 `str` (➡️ 💬, 📛 🔠 🏬). + * 💲 👉 `dict` 🆎 `float` (➡️ 💬, 🔖 🔠 🏬). + +#### 🇪🇺 + +👆 💪 📣 👈 🔢 💪 🙆 **📚 🆎**, 🖼, `int` ⚖️ `str`. + +🐍 3️⃣.6️⃣ & 🔛 (✅ 🐍 3️⃣.1️⃣0️⃣) 👆 💪 ⚙️ `Union` 🆎 ⚪️➡️ `typing` & 🚮 🔘 ⬜ 🗜 💪 🆎 🚫. + +🐍 3️⃣.1️⃣0️⃣ 📤 **🎛 ❕** 🌐❔ 👆 💪 🚮 💪 🆎 👽 ⏸ ⏸ (`|`). + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008b.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial008b_py310.py!} + ``` + +👯‍♂️ 💼 👉 ⛓ 👈 `item` 💪 `int` ⚖️ `str`. + +#### 🎲 `None` + +👆 💪 📣 👈 💲 💪 ✔️ 🆎, 💖 `str`, ✋️ 👈 ⚫️ 💪 `None`. + +🐍 3️⃣.6️⃣ & 🔛 (✅ 🐍 3️⃣.1️⃣0️⃣) 👆 💪 📣 ⚫️ 🏭 & ⚙️ `Optional` ⚪️➡️ `typing` 🕹. + +```Python hl_lines="1 4" +{!../../../docs_src/python_types/tutorial009.py!} +``` + +⚙️ `Optional[str]` ↩️ `str` 🔜 ➡️ 👨‍🎨 ℹ 👆 🔍 ❌ 🌐❔ 👆 💪 🤔 👈 💲 🕧 `str`, 🕐❔ ⚫️ 💪 🤙 `None` 💁‍♂️. + +`Optional[Something]` 🤙 ⌨ `Union[Something, None]`, 👫 🌓. + +👉 ⛓ 👈 🐍 3️⃣.1️⃣0️⃣, 👆 💪 ⚙️ `Something | None`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009.py!} + ``` + +=== "🐍 3️⃣.6️⃣ & 🔛 - 🎛" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009b.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial009_py310.py!} + ``` + +#### ⚙️ `Union` ⚖️ `Optional` + +🚥 👆 ⚙️ 🐍 ⏬ 🔛 3️⃣.1️⃣0️⃣, 📥 💁‍♂ ⚪️➡️ 👇 📶 **🤔** ☝ 🎑: + +* 👶 ❎ ⚙️ `Optional[SomeType]` +* ↩️ 👶 **⚙️ `Union[SomeType, None]`** 👶. + +👯‍♂️ 🌓 & 🔘 👫 🎏, ✋️ 👤 🔜 👍 `Union` ↩️ `Optional` ↩️ 🔤 "**📦**" 🔜 😑 🔑 👈 💲 📦, & ⚫️ 🤙 ⛓ "⚫️ 💪 `None`", 🚥 ⚫️ 🚫 📦 & ✔. + +👤 💭 `Union[SomeType, None]` 🌖 🔑 🔃 ⚫️❔ ⚫️ ⛓. + +⚫️ 🔃 🔤 & 📛. ✋️ 👈 🔤 💪 📉 ❔ 👆 & 👆 🤽‍♂ 💭 🔃 📟. + +🖼, ➡️ ✊ 👉 🔢: + +```Python hl_lines="1 4" +{!../../../docs_src/python_types/tutorial009c.py!} +``` + +🔢 `name` 🔬 `Optional[str]`, ✋️ ⚫️ **🚫 📦**, 👆 🚫🔜 🤙 🔢 🍵 🔢: + +```Python +say_hi() # Oh, no, this throws an error! 😱 +``` + +`name` 🔢 **✔** (🚫 *📦*) ↩️ ⚫️ 🚫 ✔️ 🔢 💲. , `name` 🚫 `None` 💲: + +```Python +say_hi(name=None) # This works, None is valid 🎉 +``` + +👍 📰, 🕐 👆 🔛 🐍 3️⃣.1️⃣0️⃣ 👆 🏆 🚫 ✔️ 😟 🔃 👈, 👆 🔜 💪 🎯 ⚙️ `|` 🔬 🇪🇺 🆎: + +```Python hl_lines="1 4" +{!../../../docs_src/python_types/tutorial009c_py310.py!} +``` + +& ⤴️ 👆 🏆 🚫 ✔️ 😟 🔃 📛 💖 `Optional` & `Union`. 👶 + +#### 💊 🆎 + +👉 🆎 👈 ✊ 🆎 🔢 ⬜ 🗜 🤙 **💊 🆎** ⚖️ **💊**, 🖼: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + * `List` + * `Tuple` + * `Set` + * `Dict` + * `Union` + * `Optional` + * ...& 🎏. + +=== "🐍 3️⃣.9️⃣ & 🔛" + + 👆 💪 ⚙️ 🎏 💽 🆎 💊 (⏮️ ⬜ 🗜 & 🆎 🔘): + + * `list` + * `tuple` + * `set` + * `dict` + + & 🎏 ⏮️ 🐍 3️⃣.6️⃣, ⚪️➡️ `typing` 🕹: + + * `Union` + * `Optional` + * ...& 🎏. + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + 👆 💪 ⚙️ 🎏 💽 🆎 💊 (⏮️ ⬜ 🗜 & 🆎 🔘): + + * `list` + * `tuple` + * `set` + * `dict` + + & 🎏 ⏮️ 🐍 3️⃣.6️⃣, ⚪️➡️ `typing` 🕹: + + * `Union` + * `Optional` (🎏 ⏮️ 🐍 3️⃣.6️⃣) + * ...& 🎏. + + 🐍 3️⃣.1️⃣0️⃣, 🎛 ⚙️ 💊 `Union` & `Optional`, 👆 💪 ⚙️ ⏸ ⏸ (`|`) 📣 🇪🇺 🆎. + +### 🎓 🆎 + +👆 💪 📣 🎓 🆎 🔢. + +➡️ 💬 👆 ✔️ 🎓 `Person`, ⏮️ 📛: + +```Python hl_lines="1-3" +{!../../../docs_src/python_types/tutorial010.py!} +``` + +⤴️ 👆 💪 📣 🔢 🆎 `Person`: + +```Python hl_lines="6" +{!../../../docs_src/python_types/tutorial010.py!} +``` + +& ⤴️, 🔄, 👆 🤚 🌐 👨‍🎨 🐕‍🦺: + + + +## Pydantic 🏷 + +Pydantic 🐍 🗃 🎭 📊 🔬. + +👆 📣 "💠" 💽 🎓 ⏮️ 🔢. + +& 🔠 🔢 ✔️ 🆎. + +⤴️ 👆 ✍ 👐 👈 🎓 ⏮️ 💲 & ⚫️ 🔜 ✔ 💲, 🗜 👫 ☑ 🆎 (🚥 👈 💼) & 🤝 👆 🎚 ⏮️ 🌐 💽. + +& 👆 🤚 🌐 👨‍🎨 🐕‍🦺 ⏮️ 👈 📉 🎚. + +🖼 ⚪️➡️ 🛂 Pydantic 🩺: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/python_types/tutorial011.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/python_types/tutorial011_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/python_types/tutorial011_py310.py!} + ``` + +!!! info + 💡 🌖 🔃 Pydantic, ✅ 🚮 🩺. + +**FastAPI** 🌐 ⚓️ 🔛 Pydantic. + +👆 🔜 👀 📚 🌅 🌐 👉 💡 [🔰 - 👩‍💻 🦮](tutorial/index.md){.internal-link target=_blank}. + +!!! tip + Pydantic ✔️ 🎁 🎭 🕐❔ 👆 ⚙️ `Optional` ⚖️ `Union[Something, None]` 🍵 🔢 💲, 👆 💪 ✍ 🌅 🔃 ⚫️ Pydantic 🩺 🔃 ✔ 📦 🏑. + +## 🆎 🔑 **FastAPI** + +**FastAPI** ✊ 📈 👫 🆎 🔑 📚 👜. + +⏮️ **FastAPI** 👆 📣 🔢 ⏮️ 🆎 🔑 & 👆 🤚: + +* **👨‍🎨 🐕‍🦺**. +* **🆎 ✅**. + +...and **FastAPI** uses the same declarations : + +* **🔬 📄**: ⚪️➡️ 📨 ➡ 🔢, 🔢 🔢, 🎚, 💪, 🔗, ♒️. +* **🗜 💽**: ⚪️➡️ 📨 🚚 🆎. +* **✔ 💽**: 👟 ⚪️➡️ 🔠 📨: + * 🏭 **🏧 ❌** 📨 👩‍💻 🕐❔ 📊 ❌. +* **📄** 🛠️ ⚙️ 🗄: + * ❔ ⤴️ ⚙️ 🏧 🎓 🧾 👩‍💻 🔢. + +👉 5️⃣📆 🌐 🔊 📝. 🚫 😟. 👆 🔜 👀 🌐 👉 🎯 [🔰 - 👩‍💻 🦮](tutorial/index.md){.internal-link target=_blank}. + +⚠ 👜 👈 ⚙️ 🐩 🐍 🆎, 👁 🥉 (↩️ ❎ 🌖 🎓, 👨‍🎨, ♒️), **FastAPI** 🔜 📚 👷 👆. + +!!! info + 🚥 👆 ⏪ 🚶 🔘 🌐 🔰 & 👟 🔙 👀 🌅 🔃 🆎, 👍 ℹ "🎮 🎼" ⚪️➡️ `mypy`. diff --git a/docs/em/docs/tutorial/background-tasks.md b/docs/em/docs/tutorial/background-tasks.md new file mode 100644 index 0000000000..e28ead4155 --- /dev/null +++ b/docs/em/docs/tutorial/background-tasks.md @@ -0,0 +1,102 @@ +# 🖥 📋 + +👆 💪 🔬 🖥 📋 🏃 *⏮️* 🛬 📨. + +👉 ⚠ 🛠️ 👈 💪 🔨 ⏮️ 📨, ✋️ 👈 👩‍💻 🚫 🤙 ✔️ ⌛ 🛠️ 🏁 ⏭ 📨 📨. + +👉 🔌, 🖼: + +* 📧 📨 📨 ⏮️ 🎭 🎯: + * 🔗 📧 💽 & 📨 📧 😑 "🐌" (📚 🥈), 👆 💪 📨 📨 ▶️️ ↖️ & 📨 📧 📨 🖥. +* 🏭 💽: + * 🖼, ➡️ 💬 👆 📨 📁 👈 🔜 🚶 🔘 🐌 🛠️, 👆 💪 📨 📨 "🚫" (🇺🇸🔍 2️⃣0️⃣2️⃣) & 🛠️ ⚫️ 🖥. + +## ⚙️ `BackgroundTasks` + +🥇, 🗄 `BackgroundTasks` & 🔬 🔢 👆 *➡ 🛠️ 🔢* ⏮️ 🆎 📄 `BackgroundTasks`: + +```Python hl_lines="1 13" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +**FastAPI** 🔜 ✍ 🎚 🆎 `BackgroundTasks` 👆 & 🚶‍♀️ ⚫️ 👈 🔢. + +## ✍ 📋 🔢 + +✍ 🔢 🏃 🖥 📋. + +⚫️ 🐩 🔢 👈 💪 📨 🔢. + +⚫️ 💪 `async def` ⚖️ 😐 `def` 🔢, **FastAPI** 🔜 💭 ❔ 🍵 ⚫️ ☑. + +👉 💼, 📋 🔢 🔜 ✍ 📁 (⚖ 📨 📧). + +& ✍ 🛠️ 🚫 ⚙️ `async` & `await`, 👥 🔬 🔢 ⏮️ 😐 `def`: + +```Python hl_lines="6-9" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +## 🚮 🖥 📋 + +🔘 👆 *➡ 🛠️ 🔢*, 🚶‍♀️ 👆 📋 🔢 *🖥 📋* 🎚 ⏮️ 👩‍🔬 `.add_task()`: + +```Python hl_lines="14" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +`.add_task()` 📨 ❌: + +* 📋 🔢 🏃 🖥 (`write_notification`). +* 🙆 🔁 ❌ 👈 🔜 🚶‍♀️ 📋 🔢 ✔ (`email`). +* 🙆 🇨🇻 ❌ 👈 🔜 🚶‍♀️ 📋 🔢 (`message="some notification"`). + +## 🔗 💉 + +⚙️ `BackgroundTasks` 👷 ⏮️ 🔗 💉 ⚙️, 👆 💪 📣 🔢 🆎 `BackgroundTasks` 💗 🎚: *➡ 🛠️ 🔢*, 🔗 (☑), 🎧-🔗, ♒️. + +**FastAPI** 💭 ⚫️❔ 🔠 💼 & ❔ 🏤-⚙️ 🎏 🎚, 👈 🌐 🖥 📋 🔗 👯‍♂️ & 🏃 🖥 ⏮️: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="13 15 22 25" + {!> ../../../docs_src/background_tasks/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="11 13 20 23" + {!> ../../../docs_src/background_tasks/tutorial002_py310.py!} + ``` + +👉 🖼, 📧 🔜 ✍ `log.txt` 📁 *⏮️* 📨 📨. + +🚥 📤 🔢 📨, ⚫️ 🔜 ✍ 🕹 🖥 📋. + +& ⤴️ ➕1️⃣ 🖥 📋 🏗 *➡ 🛠️ 🔢* 🔜 ✍ 📧 ⚙️ `email` ➡ 🔢. + +## 📡 ℹ + +🎓 `BackgroundTasks` 👟 🔗 ⚪️➡️ `starlette.background`. + +⚫️ 🗄/🔌 🔗 🔘 FastAPI 👈 👆 💪 🗄 ⚫️ ⚪️➡️ `fastapi` & ❎ 😫 🗄 🎛 `BackgroundTask` (🍵 `s` 🔚) ⚪️➡️ `starlette.background`. + +🕴 ⚙️ `BackgroundTasks` (& 🚫 `BackgroundTask`), ⚫️ ⤴️ 💪 ⚙️ ⚫️ *➡ 🛠️ 🔢* 🔢 & ✔️ **FastAPI** 🍵 🎂 👆, 💖 🕐❔ ⚙️ `Request` 🎚 🔗. + +⚫️ 💪 ⚙️ `BackgroundTask` 😞 FastAPI, ✋️ 👆 ✔️ ✍ 🎚 👆 📟 & 📨 💃 `Response` 🔌 ⚫️. + +👆 💪 👀 🌖 ℹ 💃 🛂 🩺 🖥 📋. + +## ⚠ + +🚥 👆 💪 🎭 🏋️ 🖥 📊 & 👆 🚫 🎯 💪 ⚫️ 🏃 🎏 🛠️ (🖼, 👆 🚫 💪 💰 💾, 🔢, ♒️), 👆 💪 💰 ⚪️➡️ ⚙️ 🎏 🦏 🧰 💖 🥒. + +👫 😑 🚚 🌖 🏗 📳, 📧/👨‍🏭 📤 👨‍💼, 💖 ✳ ⚖️ ✳, ✋️ 👫 ✔ 👆 🏃 🖥 📋 💗 🛠️, & ✴️, 💗 💽. + +👀 🖼, ✅ [🏗 🚂](../project-generation.md){.internal-link target=_blank}, 👫 🌐 🔌 🥒 ⏪ 📶. + +✋️ 🚥 👆 💪 🔐 🔢 & 🎚 ⚪️➡️ 🎏 **FastAPI** 📱, ⚖️ 👆 💪 🎭 🤪 🖥 📋 (💖 📨 📧 📨), 👆 💪 🎯 ⚙️ `BackgroundTasks`. + +## 🌃 + +🗄 & ⚙️ `BackgroundTasks` ⏮️ 🔢 *➡ 🛠️ 🔢* & 🔗 🚮 🖥 📋. diff --git a/docs/em/docs/tutorial/bigger-applications.md b/docs/em/docs/tutorial/bigger-applications.md new file mode 100644 index 0000000000..7b4694387d --- /dev/null +++ b/docs/em/docs/tutorial/bigger-applications.md @@ -0,0 +1,488 @@ +# 🦏 🈸 - 💗 📁 + +🚥 👆 🏗 🈸 ⚖️ 🕸 🛠️, ⚫️ 🛎 💼 👈 👆 💪 🚮 🌐 🔛 👁 📁. + +**FastAPI** 🚚 🏪 🧰 📊 👆 🈸 ⏪ 🚧 🌐 💪. + +!!! info + 🚥 👆 👟 ⚪️➡️ 🏺, 👉 🔜 🌓 🏺 📗. + +## 🖼 📁 📊 + +➡️ 💬 👆 ✔️ 📁 📊 💖 👉: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py +``` + +!!! tip + 📤 📚 `__init__.py` 📁: 1️⃣ 🔠 📁 ⚖️ 📁. + + 👉 ⚫️❔ ✔ 🏭 📟 ⚪️➡️ 1️⃣ 📁 🔘 ➕1️⃣. + + 🖼, `app/main.py` 👆 💪 ✔️ ⏸ 💖: + + ``` + from app.routers import items + ``` + +* `app` 📁 🔌 🌐. & ⚫️ ✔️ 🛁 📁 `app/__init__.py`, ⚫️ "🐍 📦" (🗃 "🐍 🕹"): `app`. +* ⚫️ 🔌 `app/main.py` 📁. ⚫️ 🔘 🐍 📦 (📁 ⏮️ 📁 `__init__.py`), ⚫️ "🕹" 👈 📦: `app.main`. +* 📤 `app/dependencies.py` 📁, 💖 `app/main.py`, ⚫️ "🕹": `app.dependencies`. +* 📤 📁 `app/routers/` ⏮️ ➕1️⃣ 📁 `__init__.py`, ⚫️ "🐍 📦": `app.routers`. +* 📁 `app/routers/items.py` 🔘 📦, `app/routers/`,, ⚫️ 🔁: `app.routers.items`. +* 🎏 ⏮️ `app/routers/users.py`, ⚫️ ➕1️⃣ 🔁: `app.routers.users`. +* 📤 📁 `app/internal/` ⏮️ ➕1️⃣ 📁 `__init__.py`, ⚫️ ➕1️⃣ "🐍 📦": `app.internal`. +* & 📁 `app/internal/admin.py` ➕1️⃣ 🔁: `app.internal.admin`. + + + +🎏 📁 📊 ⏮️ 🏤: + +``` +. +├── app # "app" is a Python package +│   ├── __init__.py # this file makes "app" a "Python package" +│   ├── main.py # "main" module, e.g. import app.main +│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies +│   └── routers # "routers" is a "Python subpackage" +│   │ ├── __init__.py # makes "routers" a "Python subpackage" +│   │ ├── items.py # "items" submodule, e.g. import app.routers.items +│   │ └── users.py # "users" submodule, e.g. import app.routers.users +│   └── internal # "internal" is a "Python subpackage" +│   ├── __init__.py # makes "internal" a "Python subpackage" +│   └── admin.py # "admin" submodule, e.g. import app.internal.admin +``` + +## `APIRouter` + +➡️ 💬 📁 💡 🚚 👩‍💻 🔁 `/app/routers/users.py`. + +👆 💚 ✔️ *➡ 🛠️* 🔗 👆 👩‍💻 👽 ⚪️➡️ 🎂 📟, 🚧 ⚫️ 🏗. + +✋️ ⚫️ 🍕 🎏 **FastAPI** 🈸/🕸 🛠️ (⚫️ 🍕 🎏 "🐍 📦"). + +👆 💪 ✍ *➡ 🛠️* 👈 🕹 ⚙️ `APIRouter`. + +### 🗄 `APIRouter` + +👆 🗄 ⚫️ & ✍ "👐" 🎏 🌌 👆 🔜 ⏮️ 🎓 `FastAPI`: + +```Python hl_lines="1 3" +{!../../../docs_src/bigger_applications/app/routers/users.py!} +``` + +### *➡ 🛠️* ⏮️ `APIRouter` + +& ⤴️ 👆 ⚙️ ⚫️ 📣 👆 *➡ 🛠️*. + +⚙️ ⚫️ 🎏 🌌 👆 🔜 ⚙️ `FastAPI` 🎓: + +```Python hl_lines="6 11 16" +{!../../../docs_src/bigger_applications/app/routers/users.py!} +``` + +👆 💪 💭 `APIRouter` "🐩 `FastAPI`" 🎓. + +🌐 🎏 🎛 🐕‍🦺. + +🌐 🎏 `parameters`, `responses`, `dependencies`, `tags`, ♒️. + +!!! tip + 👉 🖼, 🔢 🤙 `router`, ✋️ 👆 💪 📛 ⚫️ 👐 👆 💚. + +👥 🔜 🔌 👉 `APIRouter` 👑 `FastAPI` 📱, ✋️ 🥇, ➡️ ✅ 🔗 & ➕1️⃣ `APIRouter`. + +## 🔗 + +👥 👀 👈 👥 🔜 💪 🔗 ⚙️ 📚 🥉 🈸. + +👥 🚮 👫 👫 👍 `dependencies` 🕹 (`app/dependencies.py`). + +👥 🔜 🔜 ⚙️ 🙅 🔗 ✍ 🛃 `X-Token` 🎚: + +```Python hl_lines="1 4-6" +{!../../../docs_src/bigger_applications/app/dependencies.py!} +``` + +!!! tip + 👥 ⚙️ 💭 🎚 📉 👉 🖼. + + ✋️ 🎰 💼 👆 🔜 🤚 👍 🏁 ⚙️ 🛠️ [💂‍♂ 🚙](./security/index.md){.internal-link target=_blank}. + +## ➕1️⃣ 🕹 ⏮️ `APIRouter` + +➡️ 💬 👆 ✔️ 🔗 💡 🚚 "🏬" ⚪️➡️ 👆 🈸 🕹 `app/routers/items.py`. + +👆 ✔️ *➡ 🛠️* : + +* `/items/` +* `/items/{item_id}` + +⚫️ 🌐 🎏 📊 ⏮️ `app/routers/users.py`. + +✋️ 👥 💚 🙃 & 📉 📟 🍖. + +👥 💭 🌐 *➡ 🛠️* 👉 🕹 ✔️ 🎏: + +* ➡ `prefix`: `/items`. +* `tags`: (1️⃣ 🔖: `items`). +* ➕ `responses`. +* `dependencies`: 👫 🌐 💪 👈 `X-Token` 🔗 👥 ✍. + +, ↩️ ❎ 🌐 👈 🔠 *➡ 🛠️*, 👥 💪 🚮 ⚫️ `APIRouter`. + +```Python hl_lines="5-10 16 21" +{!../../../docs_src/bigger_applications/app/routers/items.py!} +``` + +➡ 🔠 *➡ 🛠️* ✔️ ▶️ ⏮️ `/`, 💖: + +```Python hl_lines="1" +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +...🔡 🔜 🚫 🔌 🏁 `/`. + +, 🔡 👉 💼 `/items`. + +👥 💪 🚮 📇 `tags` & ➕ `responses` 👈 🔜 ✔ 🌐 *➡ 🛠️* 🔌 👉 📻. + +& 👥 💪 🚮 📇 `dependencies` 👈 🔜 🚮 🌐 *➡ 🛠️* 📻 & 🔜 🛠️/❎ 🔠 📨 ⚒ 👫. + +!!! tip + 🗒 👈, 🌅 💖 [🔗 *➡ 🛠️ 👨‍🎨*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, 🙅‍♂ 💲 🔜 🚶‍♀️ 👆 *➡ 🛠️ 🔢*. + +🔚 🏁 👈 🏬 ➡ 🔜: + +* `/items/` +* `/items/{item_id}` + +...👥 🎯. + +* 👫 🔜 ™ ⏮️ 📇 🔖 👈 🔌 👁 🎻 `"items"`. + * 👫 "🔖" ✴️ ⚠ 🏧 🎓 🧾 ⚙️ (⚙️ 🗄). +* 🌐 👫 🔜 🔌 🔁 `responses`. +* 🌐 👫 *➡ 🛠️* 🔜 ✔️ 📇 `dependencies` 🔬/🛠️ ⏭ 👫. + * 🚥 👆 📣 🔗 🎯 *➡ 🛠️*, **👫 🔜 🛠️ 💁‍♂️**. + * 📻 🔗 🛠️ 🥇, ⤴️ [`dependencies` 👨‍🎨](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, & ⤴️ 😐 🔢 🔗. + * 👆 💪 🚮 [`Security` 🔗 ⏮️ `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. + +!!! tip + ✔️ `dependencies` `APIRouter` 💪 ⚙️, 🖼, 🚚 🤝 🎂 👪 *➡ 🛠️*. 🚥 🔗 🚫 🚮 📦 🔠 1️⃣ 👫. + +!!! check + `prefix`, `tags`, `responses`, & `dependencies` 🔢 (📚 🎏 💼) ⚒ ⚪️➡️ **FastAPI** ℹ 👆 ❎ 📟 ❎. + +### 🗄 🔗 + +👉 📟 👨‍❤‍👨 🕹 `app.routers.items`, 📁 `app/routers/items.py`. + +& 👥 💪 🤚 🔗 🔢 ⚪️➡️ 🕹 `app.dependencies`, 📁 `app/dependencies.py`. + +👥 ⚙️ ⚖ 🗄 ⏮️ `..` 🔗: + +```Python hl_lines="3" +{!../../../docs_src/bigger_applications/app/routers/items.py!} +``` + +#### ❔ ⚖ 🗄 👷 + +!!! tip + 🚥 👆 💭 👌 ❔ 🗄 👷, 😣 ⏭ 📄 🔛. + +👁 ❣ `.`, 💖: + +```Python +from .dependencies import get_token_header +``` + +🔜 ⛓: + +* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)... +* 🔎 🕹 `dependencies` (👽 📁 `app/routers/dependencies.py`)... +* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`. + +✋️ 👈 📁 🚫 🔀, 👆 🔗 📁 `app/dependencies.py`. + +💭 ❔ 👆 📱/📁 📊 👀 💖: + + + +--- + +2️⃣ ❣ `..`, 💖: + +```Python +from ..dependencies import get_token_header +``` + +⛓: + +* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)... +* 🚶 👪 📦 (📁 `app/`)... +* & 📤, 🔎 🕹 `dependencies` (📁 `app/dependencies.py`)... +* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`. + +👈 👷 ☑ ❗ 👶 + +--- + +🎏 🌌, 🚥 👥 ✔️ ⚙️ 3️⃣ ❣ `...`, 💖: + +```Python +from ...dependencies import get_token_header +``` + +that 🔜 ⛓: + +* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)... +* 🚶 👪 📦 (📁 `app/`)... +* ⤴️ 🚶 👪 👈 📦 (📤 🙅‍♂ 👪 📦, `app` 🔝 🎚 👶)... +* & 📤, 🔎 🕹 `dependencies` (📁 `app/dependencies.py`)... +* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`. + +👈 🔜 🔗 📦 🔛 `app/`, ⏮️ 🚮 👍 📁 `__init__.py`, ♒️. ✋️ 👥 🚫 ✔️ 👈. , 👈 🔜 🚮 ❌ 👆 🖼. 👶 + +✋️ 🔜 👆 💭 ❔ ⚫️ 👷, 👆 💪 ⚙️ ⚖ 🗄 👆 👍 📱 🙅‍♂ 🤔 ❔ 🏗 👫. 👶 + +### 🚮 🛃 `tags`, `responses`, & `dependencies` + +👥 🚫 ❎ 🔡 `/items` 🚫 `tags=["items"]` 🔠 *➡ 🛠️* ↩️ 👥 🚮 👫 `APIRouter`. + +✋️ 👥 💪 🚮 _🌅_ `tags` 👈 🔜 ✔ 🎯 *➡ 🛠️*, & ➕ `responses` 🎯 👈 *➡ 🛠️*: + +```Python hl_lines="30-31" +{!../../../docs_src/bigger_applications/app/routers/items.py!} +``` + +!!! tip + 👉 🏁 ➡ 🛠️ 🔜 ✔️ 🌀 🔖: `["items", "custom"]`. + + & ⚫️ 🔜 ✔️ 👯‍♂️ 📨 🧾, 1️⃣ `404` & 1️⃣ `403`. + +## 👑 `FastAPI` + +🔜, ➡️ 👀 🕹 `app/main.py`. + +📥 🌐❔ 👆 🗄 & ⚙️ 🎓 `FastAPI`. + +👉 🔜 👑 📁 👆 🈸 👈 👔 🌐 👯‍♂️. + +& 🏆 👆 ⚛ 🔜 🔜 🖖 🚮 👍 🎯 🕹, 👑 📁 🔜 🙅. + +### 🗄 `FastAPI` + +👆 🗄 & ✍ `FastAPI` 🎓 🛎. + +& 👥 💪 📣 [🌐 🔗](dependencies/global-dependencies.md){.internal-link target=_blank} 👈 🔜 🌀 ⏮️ 🔗 🔠 `APIRouter`: + +```Python hl_lines="1 3 7" +{!../../../docs_src/bigger_applications/app/main.py!} +``` + +### 🗄 `APIRouter` + +🔜 👥 🗄 🎏 🔁 👈 ✔️ `APIRouter`Ⓜ: + +```Python hl_lines="5" +{!../../../docs_src/bigger_applications/app/main.py!} +``` + +📁 `app/routers/users.py` & `app/routers/items.py` 🔁 👈 🍕 🎏 🐍 📦 `app`, 👥 💪 ⚙️ 👁 ❣ `.` 🗄 👫 ⚙️ "⚖ 🗄". + +### ❔ 🏭 👷 + +📄: + +```Python +from .routers import items, users +``` + +⛓: + +* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/main.py`) 🖖 (📁 `app/`)... +* 👀 📦 `routers` (📁 `app/routers/`)... +* & ⚪️➡️ ⚫️, 🗄 🔁 `items` (📁 `app/routers/items.py`) & `users` (📁 `app/routers/users.py`)... + +🕹 `items` 🔜 ✔️ 🔢 `router` (`items.router`). 👉 🎏 1️⃣ 👥 ✍ 📁 `app/routers/items.py`, ⚫️ `APIRouter` 🎚. + +& ⤴️ 👥 🎏 🕹 `users`. + +👥 💪 🗄 👫 💖: + +```Python +from app.routers import items, users +``` + +!!! info + 🥇 ⏬ "⚖ 🗄": + + ```Python + from .routers import items, users + ``` + + 🥈 ⏬ "🎆 🗄": + + ```Python + from app.routers import items, users + ``` + + 💡 🌅 🔃 🐍 📦 & 🕹, ✍ 🛂 🐍 🧾 🔃 🕹. + +### ❎ 📛 💥 + +👥 🏭 🔁 `items` 🔗, ↩️ 🏭 🚮 🔢 `router`. + +👉 ↩️ 👥 ✔️ ➕1️⃣ 🔢 📛 `router` 🔁 `users`. + +🚥 👥 ✔️ 🗄 1️⃣ ⏮️ 🎏, 💖: + +```Python +from .routers.items import router +from .routers.users import router +``` + +`router` ⚪️➡️ `users` 🔜 📁 1️⃣ ⚪️➡️ `items` & 👥 🚫🔜 💪 ⚙️ 👫 🎏 🕰. + +, 💪 ⚙️ 👯‍♂️ 👫 🎏 📁, 👥 🗄 🔁 🔗: + +```Python hl_lines="4" +{!../../../docs_src/bigger_applications/app/main.py!} +``` + +### 🔌 `APIRouter`Ⓜ `users` & `items` + +🔜, ➡️ 🔌 `router`Ⓜ ⚪️➡️ 🔁 `users` & `items`: + +```Python hl_lines="10-11" +{!../../../docs_src/bigger_applications/app/main.py!} +``` + +!!! info + `users.router` 🔌 `APIRouter` 🔘 📁 `app/routers/users.py`. + + & `items.router` 🔌 `APIRouter` 🔘 📁 `app/routers/items.py`. + +⏮️ `app.include_router()` 👥 💪 🚮 🔠 `APIRouter` 👑 `FastAPI` 🈸. + +⚫️ 🔜 🔌 🌐 🛣 ⚪️➡️ 👈 📻 🍕 ⚫️. + +!!! note "📡 ℹ" + ⚫️ 🔜 🤙 🔘 ✍ *➡ 🛠️* 🔠 *➡ 🛠️* 👈 📣 `APIRouter`. + + , ⛅ 🎑, ⚫️ 🔜 🤙 👷 🚥 🌐 🎏 👁 📱. + +!!! check + 👆 🚫 ✔️ 😟 🔃 🎭 🕐❔ ✅ 📻. + + 👉 🔜 ✊ ⏲ & 🔜 🕴 🔨 🕴. + + ⚫️ 🏆 🚫 📉 🎭. 👶 + +### 🔌 `APIRouter` ⏮️ 🛃 `prefix`, `tags`, `responses`, & `dependencies` + +🔜, ➡️ 🌈 👆 🏢 🤝 👆 `app/internal/admin.py` 📁. + +⚫️ 🔌 `APIRouter` ⏮️ 📡 *➡ 🛠️* 👈 👆 🏢 💰 🖖 📚 🏗. + +👉 🖼 ⚫️ 🔜 💎 🙅. ✋️ ➡️ 💬 👈 ↩️ ⚫️ 💰 ⏮️ 🎏 🏗 🏢, 👥 🚫🔜 🔀 ⚫️ & 🚮 `prefix`, `dependencies`, `tags`, ♒️. 🔗 `APIRouter`: + +```Python hl_lines="3" +{!../../../docs_src/bigger_applications/app/internal/admin.py!} +``` + +✋️ 👥 💚 ⚒ 🛃 `prefix` 🕐❔ ✅ `APIRouter` 👈 🌐 🚮 *➡ 🛠️* ▶️ ⏮️ `/admin`, 👥 💚 🔐 ⚫️ ⏮️ `dependencies` 👥 ⏪ ✔️ 👉 🏗, & 👥 💚 🔌 `tags` & `responses`. + +👥 💪 📣 🌐 👈 🍵 ✔️ 🔀 ⏮️ `APIRouter` 🚶‍♀️ 👈 🔢 `app.include_router()`: + +```Python hl_lines="14-17" +{!../../../docs_src/bigger_applications/app/main.py!} +``` + +👈 🌌, ⏮️ `APIRouter` 🔜 🚧 ⚗, 👥 💪 💰 👈 🎏 `app/internal/admin.py` 📁 ⏮️ 🎏 🏗 🏢. + +🏁 👈 👆 📱, 🔠 *➡ 🛠️* ⚪️➡️ `admin` 🕹 🔜 ✔️: + +* 🔡 `/admin`. +* 🔖 `admin`. +* 🔗 `get_token_header`. +* 📨 `418`. 👶 + +✋️ 👈 🔜 🕴 📉 👈 `APIRouter` 👆 📱, 🚫 🙆 🎏 📟 👈 ⚙️ ⚫️. + +, 🖼, 🎏 🏗 💪 ⚙️ 🎏 `APIRouter` ⏮️ 🎏 🤝 👩‍🔬. + +### 🔌 *➡ 🛠️* + +👥 💪 🚮 *➡ 🛠️* 🔗 `FastAPI` 📱. + +📥 👥 ⚫️... 🎦 👈 👥 💪 🤷: + +```Python hl_lines="21-23" +{!../../../docs_src/bigger_applications/app/main.py!} +``` + +& ⚫️ 🔜 👷 ☑, 👯‍♂️ ⏮️ 🌐 🎏 *➡ 🛠️* 🚮 ⏮️ `app.include_router()`. + +!!! info "📶 📡 ℹ" + **🗒**: 👉 📶 📡 ℹ 👈 👆 🎲 💪 **🚶**. + + --- + + `APIRouter`Ⓜ 🚫 "🗻", 👫 🚫 👽 ⚪️➡️ 🎂 🈸. + + 👉 ↩️ 👥 💚 🔌 👫 *➡ 🛠️* 🗄 🔗 & 👩‍💻 🔢. + + 👥 🚫🔜 ❎ 👫 & "🗻" 👫 ➡ 🎂, *➡ 🛠️* "🖖" (🏤-✍), 🚫 🔌 🔗. + +## ✅ 🏧 🛠️ 🩺 + +🔜, 🏃 `uvicorn`, ⚙️ 🕹 `app.main` & 🔢 `app`: + +
+ +```console +$ uvicorn app.main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +& 📂 🩺 http://127.0.0.1:8000/docs. + +👆 🔜 👀 🏧 🛠️ 🩺, ✅ ➡ ⚪️➡️ 🌐 🔁, ⚙️ ☑ ➡ (& 🔡) & ☑ 🔖: + + + +## 🔌 🎏 📻 💗 🕰 ⏮️ 🎏 `prefix` + +👆 💪 ⚙️ `.include_router()` 💗 🕰 ⏮️ *🎏* 📻 ⚙️ 🎏 🔡. + +👉 💪 ⚠, 🖼, 🎦 🎏 🛠️ 🔽 🎏 🔡, ✅ `/api/v1` & `/api/latest`. + +👉 🏧 ⚙️ 👈 👆 5️⃣📆 🚫 🤙 💪, ✋️ ⚫️ 📤 💼 👆. + +## 🔌 `APIRouter` ➕1️⃣ + +🎏 🌌 👆 💪 🔌 `APIRouter` `FastAPI` 🈸, 👆 💪 🔌 `APIRouter` ➕1️⃣ `APIRouter` ⚙️: + +```Python +router.include_router(other_router) +``` + +⚒ 💭 👆 ⚫️ ⏭ 🔌 `router` `FastAPI` 📱, 👈 *➡ 🛠️* ⚪️➡️ `other_router` 🔌. diff --git a/docs/em/docs/tutorial/body-fields.md b/docs/em/docs/tutorial/body-fields.md new file mode 100644 index 0000000000..9f2c914f4b --- /dev/null +++ b/docs/em/docs/tutorial/body-fields.md @@ -0,0 +1,68 @@ +# 💪 - 🏑 + +🎏 🌌 👆 💪 📣 🌖 🔬 & 🗃 *➡ 🛠️ 🔢* 🔢 ⏮️ `Query`, `Path` & `Body`, 👆 💪 📣 🔬 & 🗃 🔘 Pydantic 🏷 ⚙️ Pydantic `Field`. + +## 🗄 `Field` + +🥇, 👆 ✔️ 🗄 ⚫️: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="4" + {!> ../../../docs_src/body_fields/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="2" + {!> ../../../docs_src/body_fields/tutorial001_py310.py!} + ``` + +!!! warning + 👀 👈 `Field` 🗄 🔗 ⚪️➡️ `pydantic`, 🚫 ⚪️➡️ `fastapi` 🌐 🎂 (`Query`, `Path`, `Body`, ♒️). + +## 📣 🏷 🔢 + +👆 💪 ⤴️ ⚙️ `Field` ⏮️ 🏷 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="11-14" + {!> ../../../docs_src/body_fields/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="9-12" + {!> ../../../docs_src/body_fields/tutorial001_py310.py!} + ``` + +`Field` 👷 🎏 🌌 `Query`, `Path` & `Body`, ⚫️ ✔️ 🌐 🎏 🔢, ♒️. + +!!! note "📡 ℹ" + 🤙, `Query`, `Path` & 🎏 👆 🔜 👀 ⏭ ✍ 🎚 🏿 ⚠ `Param` 🎓, ❔ ⚫️ 🏿 Pydantic `FieldInfo` 🎓. + + & Pydantic `Field` 📨 👐 `FieldInfo` 👍. + + `Body` 📨 🎚 🏿 `FieldInfo` 🔗. & 📤 🎏 👆 🔜 👀 ⏪ 👈 🏿 `Body` 🎓. + + 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. + +!!! tip + 👀 ❔ 🔠 🏷 🔢 ⏮️ 🆎, 🔢 💲 & `Field` ✔️ 🎏 📊 *➡ 🛠️ 🔢* 🔢, ⏮️ `Field` ↩️ `Path`, `Query` & `Body`. + +## 🚮 ➕ ℹ + +👆 💪 📣 ➕ ℹ `Field`, `Query`, `Body`, ♒️. & ⚫️ 🔜 🔌 🏗 🎻 🔗. + +👆 🔜 💡 🌅 🔃 ❎ ➕ ℹ ⏪ 🩺, 🕐❔ 🏫 📣 🖼. + +!!! warning + ➕ 🔑 🚶‍♀️ `Field` 🔜 🎁 📉 🗄 🔗 👆 🈸. + 👫 🔑 5️⃣📆 🚫 🎯 🍕 🗄 🔧, 🗄 🧰, 🖼 [🗄 💳](https://validator.swagger.io/), 5️⃣📆 🚫 👷 ⏮️ 👆 🏗 🔗. + +## 🌃 + +👆 💪 ⚙️ Pydantic `Field` 📣 ➕ 🔬 & 🗃 🏷 🔢. + +👆 💪 ⚙️ ➕ 🇨🇻 ❌ 🚶‍♀️ 🌖 🎻 🔗 🗃. diff --git a/docs/em/docs/tutorial/body-multiple-params.md b/docs/em/docs/tutorial/body-multiple-params.md new file mode 100644 index 0000000000..9ada7dee10 --- /dev/null +++ b/docs/em/docs/tutorial/body-multiple-params.md @@ -0,0 +1,213 @@ +# 💪 - 💗 🔢 + +🔜 👈 👥 ✔️ 👀 ❔ ⚙️ `Path` & `Query`, ➡️ 👀 🌅 🏧 ⚙️ 📨 💪 📄. + +## 🌀 `Path`, `Query` & 💪 🔢 + +🥇, ↗️, 👆 💪 🌀 `Path`, `Query` & 📨 💪 🔢 📄 ➡ & **FastAPI** 🔜 💭 ⚫️❔. + +& 👆 💪 📣 💪 🔢 📦, ⚒ 🔢 `None`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="19-21" + {!> ../../../docs_src/body_multiple_params/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17-19" + {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} + ``` + +!!! note + 👀 👈, 👉 💼, `item` 👈 🔜 ✊ ⚪️➡️ 💪 📦. ⚫️ ✔️ `None` 🔢 💲. + +## 💗 💪 🔢 + +⏮️ 🖼, *➡ 🛠️* 🔜 ⌛ 🎻 💪 ⏮️ 🔢 `Item`, 💖: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +✋️ 👆 💪 📣 💗 💪 🔢, ✅ `item` & `user`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="22" + {!> ../../../docs_src/body_multiple_params/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} + ``` + +👉 💼, **FastAPI** 🔜 👀 👈 📤 🌅 🌘 1️⃣ 💪 🔢 🔢 (2️⃣ 🔢 👈 Pydantic 🏷). + +, ⚫️ 🔜 ⤴️ ⚙️ 🔢 📛 🔑 (🏑 📛) 💪, & ⌛ 💪 💖: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + } +} +``` + +!!! note + 👀 👈 ✋️ `item` 📣 🎏 🌌 ⏭, ⚫️ 🔜 ⌛ 🔘 💪 ⏮️ 🔑 `item`. + + +**FastAPI** 🔜 🏧 🛠️ ⚪️➡️ 📨, 👈 🔢 `item` 📨 ⚫️ 🎯 🎚 & 🎏 `user`. + +⚫️ 🔜 🎭 🔬 ⚗ 💽, & 🔜 📄 ⚫️ 💖 👈 🗄 🔗 & 🏧 🩺. + +## ⭐ 💲 💪 + +🎏 🌌 📤 `Query` & `Path` 🔬 ➕ 💽 🔢 & ➡ 🔢, **FastAPI** 🚚 🌓 `Body`. + +🖼, ↔ ⏮️ 🏷, 👆 💪 💭 👈 👆 💚 ✔️ ➕1️⃣ 🔑 `importance` 🎏 💪, 🥈 `item` & `user`. + +🚥 👆 📣 ⚫️, ↩️ ⚫️ ⭐ 💲, **FastAPI** 🔜 🤔 👈 ⚫️ 🔢 🔢. + +✋️ 👆 💪 💡 **FastAPI** 😥 ⚫️ ➕1️⃣ 💪 🔑 ⚙️ `Body`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="22" + {!> ../../../docs_src/body_multiple_params/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} + ``` + +👉 💼, **FastAPI** 🔜 ⌛ 💪 💖: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + }, + "importance": 5 +} +``` + +🔄, ⚫️ 🔜 🗜 📊 🆎, ✔, 📄, ♒️. + +## 💗 💪 = & 🔢 + +↗️, 👆 💪 📣 🌖 🔢 🔢 🕐❔ 👆 💪, 🌖 🙆 💪 🔢. + +, 🔢, ⭐ 💲 🔬 🔢 🔢, 👆 🚫 ✔️ 🎯 🚮 `Query`, 👆 💪: + +```Python +q: Union[str, None] = None +``` + +⚖️ 🐍 3️⃣.1️⃣0️⃣ & 🔛: + +```Python +q: str | None = None +``` + +🖼: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="26" + {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} + ``` + +!!! info + `Body` ✔️ 🌐 🎏 ➕ 🔬 & 🗃 🔢 `Query`,`Path` & 🎏 👆 🔜 👀 ⏪. + +## ⏯ 👁 💪 🔢 + +➡️ 💬 👆 🕴 ✔️ 👁 `item` 💪 🔢 ⚪️➡️ Pydantic 🏷 `Item`. + +🔢, **FastAPI** 🔜 ⤴️ ⌛ 🚮 💪 🔗. + +✋️ 🚥 👆 💚 ⚫️ ⌛ 🎻 ⏮️ 🔑 `item` & 🔘 ⚫️ 🏷 🎚, ⚫️ 🔨 🕐❔ 👆 📣 ➕ 💪 🔢, 👆 💪 ⚙️ 🎁 `Body` 🔢 `embed`: + +```Python +item: Item = Body(embed=True) +``` + +: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="17" + {!> ../../../docs_src/body_multiple_params/tutorial005.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="15" + {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} + ``` + +👉 💼 **FastAPI** 🔜 ⌛ 💪 💖: + +```JSON hl_lines="2" +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + } +} +``` + +↩️: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +## 🌃 + +👆 💪 🚮 💗 💪 🔢 👆 *➡ 🛠️ 🔢*, ✋️ 📨 💪 🕴 ✔️ 👁 💪. + +✋️ **FastAPI** 🔜 🍵 ⚫️, 🤝 👆 ☑ 📊 👆 🔢, & ✔ & 📄 ☑ 🔗 *➡ 🛠️*. + +👆 💪 📣 ⭐ 💲 📨 🍕 💪. + +& 👆 💪 💡 **FastAPI** ⏯ 💪 🔑 🕐❔ 📤 🕴 👁 🔢 📣. diff --git a/docs/em/docs/tutorial/body-nested-models.md b/docs/em/docs/tutorial/body-nested-models.md new file mode 100644 index 0000000000..f4bd50f5cb --- /dev/null +++ b/docs/em/docs/tutorial/body-nested-models.md @@ -0,0 +1,382 @@ +# 💪 - 🔁 🏷 + +⏮️ **FastAPI**, 👆 💪 🔬, ✔, 📄, & ⚙️ 🎲 🙇 🐦 🏷 (👏 Pydantic). + +## 📇 🏑 + +👆 💪 🔬 🔢 🏾. 🖼, 🐍 `list`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} + ``` + +👉 🔜 ⚒ `tags` 📇, 👐 ⚫️ 🚫 📣 🆎 🔣 📇. + +## 📇 🏑 ⏮️ 🆎 🔢 + +✋️ 🐍 ✔️ 🎯 🌌 📣 📇 ⏮️ 🔗 🆎, ⚖️ "🆎 🔢": + +### 🗄 ⌨ `List` + +🐍 3️⃣.9️⃣ & 🔛 👆 💪 ⚙️ 🐩 `list` 📣 👫 🆎 ✍ 👥 🔜 👀 🔛. 👶 + +✋️ 🐍 ⏬ ⏭ 3️⃣.9️⃣ (3️⃣.6️⃣ & 🔛), 👆 🥇 💪 🗄 `List` ⚪️➡️ 🐩 🐍 `typing` 🕹: + +```Python hl_lines="1" +{!> ../../../docs_src/body_nested_models/tutorial002.py!} +``` + +### 📣 `list` ⏮️ 🆎 🔢 + +📣 🆎 👈 ✔️ 🆎 🔢 (🔗 🆎), 💖 `list`, `dict`, `tuple`: + +* 🚥 👆 🐍 ⏬ 🔅 🌘 3️⃣.9️⃣, 🗄 👫 🌓 ⏬ ⚪️➡️ `typing` 🕹 +* 🚶‍♀️ 🔗 🆎(Ⓜ) "🆎 🔢" ⚙️ ⬜ 🗜: `[` & `]` + +🐍 3️⃣.9️⃣ ⚫️ 🔜: + +```Python +my_list: list[str] +``` + +⏬ 🐍 ⏭ 3️⃣.9️⃣, ⚫️ 🔜: + +```Python +from typing import List + +my_list: List[str] +``` + +👈 🌐 🐩 🐍 ❕ 🆎 📄. + +⚙️ 👈 🎏 🐩 ❕ 🏷 🔢 ⏮️ 🔗 🆎. + +, 👆 🖼, 👥 💪 ⚒ `tags` 🎯 "📇 🎻": + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial002.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial002_py310.py!} + ``` + +## ⚒ 🆎 + +✋️ ⤴️ 👥 💭 🔃 ⚫️, & 🤔 👈 🔖 🚫🔜 🚫 🔁, 👫 🔜 🎲 😍 🎻. + +& 🐍 ✔️ 🎁 💽 🆎 ⚒ 😍 🏬, `set`. + +⤴️ 👥 💪 📣 `tags` ⚒ 🎻: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 14" + {!> ../../../docs_src/body_nested_models/tutorial003.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial003_py310.py!} + ``` + +⏮️ 👉, 🚥 👆 📨 📨 ⏮️ ❎ 📊, ⚫️ 🔜 🗜 ⚒ 😍 🏬. + +& 🕐❔ 👆 🔢 👈 📊, 🚥 ℹ ✔️ ❎, ⚫️ 🔜 🔢 ⚒ 😍 🏬. + +& ⚫️ 🔜 ✍ / 📄 ➡️ 💁‍♂️. + +## 🐦 🏷 + +🔠 🔢 Pydantic 🏷 ✔️ 🆎. + +✋️ 👈 🆎 💪 ⚫️ ➕1️⃣ Pydantic 🏷. + +, 👆 💪 📣 🙇 🐦 🎻 "🎚" ⏮️ 🎯 🔢 📛, 🆎 & 🔬. + +🌐 👈, 🎲 🐦. + +### 🔬 📊 + +🖼, 👥 💪 🔬 `Image` 🏷: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9-11" + {!> ../../../docs_src/body_nested_models/tutorial004.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="9-11" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7-9" + {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} + ``` + +### ⚙️ 📊 🆎 + +& ⤴️ 👥 💪 ⚙️ ⚫️ 🆎 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial004.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} + ``` + +👉 🔜 ⛓ 👈 **FastAPI** 🔜 ⌛ 💪 🎏: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +🔄, 🤸 👈 📄, ⏮️ **FastAPI** 👆 🤚: + +* 👨‍🎨 🐕‍🦺 (🛠️, ♒️), 🐦 🏷 +* 💽 🛠️ +* 💽 🔬 +* 🏧 🧾 + +## 🎁 🆎 & 🔬 + +↖️ ⚪️➡️ 😐 ⭐ 🆎 💖 `str`, `int`, `float`, ♒️. 👆 💪 ⚙️ 🌅 🏗 ⭐ 🆎 👈 😖 ⚪️➡️ `str`. + +👀 🌐 🎛 👆 ✔️, 🛒 🩺 Pydantic 😍 🆎. 👆 🔜 👀 🖼 ⏭ 📃. + +🖼, `Image` 🏷 👥 ✔️ `url` 🏑, 👥 💪 📣 ⚫️ ↩️ `str`, Pydantic `HttpUrl`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="4 10" + {!> ../../../docs_src/body_nested_models/tutorial005.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="4 10" + {!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="2 8" + {!> ../../../docs_src/body_nested_models/tutorial005_py310.py!} + ``` + +🎻 🔜 ✅ ☑ 📛, & 📄 🎻 🔗 / 🗄 ✅. + +## 🔢 ⏮️ 📇 📊 + +👆 💪 ⚙️ Pydantic 🏷 🏾 `list`, `set`, ♒️: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial006.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_nested_models/tutorial006_py310.py!} + ``` + +👉 🔜 ⌛ (🗜, ✔, 📄, ♒️) 🎻 💪 💖: + +```JSON hl_lines="11" +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +!!! info + 👀 ❔ `images` 🔑 🔜 ✔️ 📇 🖼 🎚. + +## 🙇 🐦 🏷 + +👆 💪 🔬 🎲 🙇 🐦 🏷: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9 14 20 23 27" + {!> ../../../docs_src/body_nested_models/tutorial007.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="9 14 20 23 27" + {!> ../../../docs_src/body_nested_models/tutorial007_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7 12 18 21 25" + {!> ../../../docs_src/body_nested_models/tutorial007_py310.py!} + ``` + +!!! info + 👀 ❔ `Offer` ✔️ 📇 `Item`Ⓜ, ❔ 🔄 ✔️ 📦 📇 `Image`Ⓜ + +## 💪 😁 📇 + +🚥 🔝 🎚 💲 🎻 💪 👆 ⌛ 🎻 `array` (🐍 `list`), 👆 💪 📣 🆎 🔢 🔢, 🎏 Pydantic 🏷: + +```Python +images: List[Image] +``` + +⚖️ 🐍 3️⃣.9️⃣ & 🔛: + +```Python +images: list[Image] +``` + +: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="15" + {!> ../../../docs_src/body_nested_models/tutorial008.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="13" + {!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} + ``` + +## 👨‍🎨 🐕‍🦺 🌐 + +& 👆 🤚 👨‍🎨 🐕‍🦺 🌐. + +🏬 🔘 📇: + + + +👆 🚫 🚫 🤚 👉 😇 👨‍🎨 🐕‍🦺 🚥 👆 👷 🔗 ⏮️ `dict` ↩️ Pydantic 🏷. + +✋️ 👆 🚫 ✔️ 😟 🔃 👫 👯‍♂️, 📨 #️⃣ 🗜 🔁 & 👆 🔢 🗜 🔁 🎻 💁‍♂️. + +## 💪 ❌ `dict`Ⓜ + +👆 💪 📣 💪 `dict` ⏮️ 🔑 🆎 & 💲 🎏 🆎. + +🍵 ✔️ 💭 ⏪ ⚫️❔ ☑ 🏑/🔢 📛 (🔜 💼 ⏮️ Pydantic 🏷). + +👉 🔜 ⚠ 🚥 👆 💚 📨 🔑 👈 👆 🚫 ⏪ 💭. + +--- + +🎏 ⚠ 💼 🕐❔ 👆 💚 ✔️ 🔑 🎏 🆎, ✅ `int`. + +👈 ⚫️❔ 👥 🔜 👀 📥. + +👉 💼, 👆 🔜 🚫 🙆 `dict` 📏 ⚫️ ✔️ `int` 🔑 ⏮️ `float` 💲: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/body_nested_models/tutorial009.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/body_nested_models/tutorial009_py39.py!} + ``` + +!!! tip + ✔️ 🤯 👈 🎻 🕴 🐕‍🦺 `str` 🔑. + + ✋️ Pydantic ✔️ 🏧 💽 🛠️. + + 👉 ⛓ 👈, ✋️ 👆 🛠️ 👩‍💻 💪 🕴 📨 🎻 🔑, 📏 👈 🎻 🔌 😁 🔢, Pydantic 🔜 🗜 👫 & ✔ 👫. + + & `dict` 👆 📨 `weights` 🔜 🤙 ✔️ `int` 🔑 & `float` 💲. + +## 🌃 + +⏮️ **FastAPI** 👆 ✔️ 🔆 💪 🚚 Pydantic 🏷, ⏪ 🚧 👆 📟 🙅, 📏 & 😍. + +✋️ ⏮️ 🌐 💰: + +* 👨‍🎨 🐕‍🦺 (🛠️ 🌐 ❗) +* 💽 🛠️ (.Ⓜ.. ✍ / 🛠️) +* 💽 🔬 +* 🔗 🧾 +* 🏧 🩺 diff --git a/docs/em/docs/tutorial/body-updates.md b/docs/em/docs/tutorial/body-updates.md new file mode 100644 index 0000000000..98058ab526 --- /dev/null +++ b/docs/em/docs/tutorial/body-updates.md @@ -0,0 +1,155 @@ +# 💪 - ℹ + +## ℹ ❎ ⏮️ `PUT` + +ℹ 🏬 👆 💪 ⚙️ 🇺🇸🔍 `PUT` 🛠️. + +👆 💪 ⚙️ `jsonable_encoder` 🗜 🔢 💽 📊 👈 💪 🏪 🎻 (✅ ⏮️ ☁ 💽). 🖼, 🏭 `datetime` `str`. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="30-35" + {!> ../../../docs_src/body_updates/tutorial001.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="30-35" + {!> ../../../docs_src/body_updates/tutorial001_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="28-33" + {!> ../../../docs_src/body_updates/tutorial001_py310.py!} + ``` + +`PUT` ⚙️ 📨 💽 👈 🔜 ❎ ♻ 💽. + +### ⚠ 🔃 ❎ + +👈 ⛓ 👈 🚥 👆 💚 ℹ 🏬 `bar` ⚙️ `PUT` ⏮️ 💪 ⚗: + +```Python +{ + "name": "Barz", + "price": 3, + "description": None, +} +``` + +↩️ ⚫️ 🚫 🔌 ⏪ 🏪 🔢 `"tax": 20.2`, 🔢 🏷 🔜 ✊ 🔢 💲 `"tax": 10.5`. + +& 📊 🔜 🖊 ⏮️ 👈 "🆕" `tax` `10.5`. + +## 🍕 ℹ ⏮️ `PATCH` + +👆 💪 ⚙️ 🇺🇸🔍 `PATCH` 🛠️ *🍕* ℹ 💽. + +👉 ⛓ 👈 👆 💪 📨 🕴 💽 👈 👆 💚 ℹ, 🍂 🎂 🐣. + +!!! Note + `PATCH` 🌘 🛎 ⚙️ & 💭 🌘 `PUT`. + + & 📚 🏉 ⚙️ 🕴 `PUT`, 🍕 ℹ. + + 👆 **🆓** ⚙️ 👫 👐 👆 💚, **FastAPI** 🚫 🚫 🙆 🚫. + + ✋️ 👉 🦮 🎦 👆, 🌖 ⚖️ 🌘, ❔ 👫 🎯 ⚙️. + +### ⚙️ Pydantic `exclude_unset` 🔢 + +🚥 👆 💚 📨 🍕 ℹ, ⚫️ 📶 ⚠ ⚙️ 🔢 `exclude_unset` Pydantic 🏷 `.dict()`. + +💖 `item.dict(exclude_unset=True)`. + +👈 🔜 🏗 `dict` ⏮️ 🕴 💽 👈 ⚒ 🕐❔ 🏗 `item` 🏷, 🚫 🔢 💲. + +⤴️ 👆 💪 ⚙️ 👉 🏗 `dict` ⏮️ 🕴 💽 👈 ⚒ (📨 📨), 🚫 🔢 💲: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="34" + {!> ../../../docs_src/body_updates/tutorial002.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="34" + {!> ../../../docs_src/body_updates/tutorial002_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="32" + {!> ../../../docs_src/body_updates/tutorial002_py310.py!} + ``` + +### ⚙️ Pydantic `update` 🔢 + +🔜, 👆 💪 ✍ 📁 ♻ 🏷 ⚙️ `.copy()`, & 🚶‍♀️ `update` 🔢 ⏮️ `dict` ⚗ 💽 ℹ. + +💖 `stored_item_model.copy(update=update_data)`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="35" + {!> ../../../docs_src/body_updates/tutorial002.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="35" + {!> ../../../docs_src/body_updates/tutorial002_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="33" + {!> ../../../docs_src/body_updates/tutorial002_py310.py!} + ``` + +### 🍕 ℹ 🌃 + +📄, ✔ 🍕 ℹ 👆 🔜: + +* (⚗) ⚙️ `PATCH` ↩️ `PUT`. +* 🗃 🏪 💽. +* 🚮 👈 💽 Pydantic 🏷. +* 🏗 `dict` 🍵 🔢 💲 ⚪️➡️ 🔢 🏷 (⚙️ `exclude_unset`). + * 👉 🌌 👆 💪 ℹ 🕴 💲 🤙 ⚒ 👩‍💻, ↩️ 🔐 💲 ⏪ 🏪 ⏮️ 🔢 💲 👆 🏷. +* ✍ 📁 🏪 🏷, 🛠️ ⚫️ 🔢 ⏮️ 📨 🍕 ℹ (⚙️ `update` 🔢). +* 🗜 📁 🏷 🕳 👈 💪 🏪 👆 💽 (🖼, ⚙️ `jsonable_encoder`). + * 👉 ⭐ ⚙️ 🏷 `.dict()` 👩‍🔬 🔄, ✋️ ⚫️ ⚒ 💭 (& 🗜) 💲 💽 🆎 👈 💪 🗜 🎻, 🖼, `datetime` `str`. +* 🖊 💽 👆 💽. +* 📨 ℹ 🏷. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="30-37" + {!> ../../../docs_src/body_updates/tutorial002.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="30-37" + {!> ../../../docs_src/body_updates/tutorial002_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="28-35" + {!> ../../../docs_src/body_updates/tutorial002_py310.py!} + ``` + +!!! tip + 👆 💪 🤙 ⚙️ 👉 🎏 ⚒ ⏮️ 🇺🇸🔍 `PUT` 🛠️. + + ✋️ 🖼 📥 ⚙️ `PATCH` ↩️ ⚫️ ✍ 👫 ⚙️ 💼. + +!!! note + 👀 👈 🔢 🏷 ✔. + + , 🚥 👆 💚 📨 🍕 ℹ 👈 💪 🚫 🌐 🔢, 👆 💪 ✔️ 🏷 ⏮️ 🌐 🔢 ™ 📦 (⏮️ 🔢 💲 ⚖️ `None`). + + 🔬 ⚪️➡️ 🏷 ⏮️ 🌐 📦 💲 **ℹ** & 🏷 ⏮️ ✔ 💲 **🏗**, 👆 💪 ⚙️ 💭 🔬 [➕ 🏷](extra-models.md){.internal-link target=_blank}. diff --git a/docs/em/docs/tutorial/body.md b/docs/em/docs/tutorial/body.md new file mode 100644 index 0000000000..ca2f113bf4 --- /dev/null +++ b/docs/em/docs/tutorial/body.md @@ -0,0 +1,213 @@ +# 📨 💪 + +🕐❔ 👆 💪 📨 📊 ⚪️➡️ 👩‍💻 (➡️ 💬, 🖥) 👆 🛠️, 👆 📨 ⚫️ **📨 💪**. + +**📨** 💪 📊 📨 👩‍💻 👆 🛠️. **📨** 💪 💽 👆 🛠️ 📨 👩‍💻. + +👆 🛠️ 🌖 🕧 ✔️ 📨 **📨** 💪. ✋️ 👩‍💻 🚫 🎯 💪 📨 **📨** 💪 🌐 🕰. + +📣 **📨** 💪, 👆 ⚙️ Pydantic 🏷 ⏮️ 🌐 👫 🏋️ & 💰. + +!!! info + 📨 💽, 👆 🔜 ⚙️ 1️⃣: `POST` (🌅 ⚠), `PUT`, `DELETE` ⚖️ `PATCH`. + + 📨 💪 ⏮️ `GET` 📨 ✔️ ⚠ 🎭 🔧, 👐, ⚫️ 🐕‍🦺 FastAPI, 🕴 📶 🏗/😕 ⚙️ 💼. + + ⚫️ 🚫, 🎓 🩺 ⏮️ 🦁 🎚 🏆 🚫 🎦 🧾 💪 🕐❔ ⚙️ `GET`, & 🗳 🖕 💪 🚫 🐕‍🦺 ⚫️. + +## 🗄 Pydantic `BaseModel` + +🥇, 👆 💪 🗄 `BaseModel` ⚪️➡️ `pydantic`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="4" + {!> ../../../docs_src/body/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="2" + {!> ../../../docs_src/body/tutorial001_py310.py!} + ``` + +## ✍ 👆 💽 🏷 + +⤴️ 👆 📣 👆 💽 🏷 🎓 👈 😖 ⚪️➡️ `BaseModel`. + +⚙️ 🐩 🐍 🆎 🌐 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="7-11" + {!> ../../../docs_src/body/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="5-9" + {!> ../../../docs_src/body/tutorial001_py310.py!} + ``` + +🎏 🕐❔ 📣 🔢 🔢, 🕐❔ 🏷 🔢 ✔️ 🔢 💲, ⚫️ 🚫 ✔. ⏪, ⚫️ ✔. ⚙️ `None` ⚒ ⚫️ 📦. + +🖼, 👉 🏷 🔛 📣 🎻 "`object`" (⚖️ 🐍 `dict`) 💖: + +```JSON +{ + "name": "Foo", + "description": "An optional description", + "price": 45.2, + "tax": 3.5 +} +``` + +... `description` & `tax` 📦 (⏮️ 🔢 💲 `None`), 👉 🎻 "`object`" 🔜 ☑: + +```JSON +{ + "name": "Foo", + "price": 45.2 +} +``` + +## 📣 ⚫️ 🔢 + +🚮 ⚫️ 👆 *➡ 🛠️*, 📣 ⚫️ 🎏 🌌 👆 📣 ➡ & 🔢 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/body/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="16" + {!> ../../../docs_src/body/tutorial001_py310.py!} + ``` + +...& 📣 🚮 🆎 🏷 👆 ✍, `Item`. + +## 🏁 + +⏮️ 👈 🐍 🆎 📄, **FastAPI** 🔜: + +* ✍ 💪 📨 🎻. +* 🗜 🔗 🆎 (🚥 💪). +* ✔ 💽. + * 🚥 💽 ❌, ⚫️ 🔜 📨 👌 & 🆑 ❌, ☠️ ⚫️❔ 🌐❔ & ⚫️❔ ❌ 📊. +* 🤝 👆 📨 📊 🔢 `item`. + * 👆 📣 ⚫️ 🔢 🆎 `Item`, 👆 🔜 ✔️ 🌐 👨‍🎨 🐕‍🦺 (🛠️, ♒️) 🌐 🔢 & 👫 🆎. +* 🏗 🎻 🔗 🔑 👆 🏷, 👆 💪 ⚙️ 👫 🙆 🙆 👆 💖 🚥 ⚫️ ⚒ 🔑 👆 🏗. +* 👈 🔗 🔜 🍕 🏗 🗄 🔗, & ⚙️ 🏧 🧾 . + +## 🏧 🩺 + +🎻 🔗 👆 🏷 🔜 🍕 👆 🗄 🏗 🔗, & 🔜 🎦 🎓 🛠️ 🩺: + + + +& 🔜 ⚙️ 🛠️ 🩺 🔘 🔠 *➡ 🛠️* 👈 💪 👫: + + + +## 👨‍🎨 🐕‍🦺 + +👆 👨‍🎨, 🔘 👆 🔢 👆 🔜 🤚 🆎 🔑 & 🛠️ 🌐 (👉 🚫🔜 🔨 🚥 👆 📨 `dict` ↩️ Pydantic 🏷): + + + +👆 🤚 ❌ ✅ ❌ 🆎 🛠️: + + + +👉 🚫 🤞, 🎂 🛠️ 🏗 🤭 👈 🔧. + +& ⚫️ 🙇 💯 🔧 🌓, ⏭ 🙆 🛠️, 🚚 ⚫️ 🔜 👷 ⏮️ 🌐 👨‍🎨. + +📤 🔀 Pydantic ⚫️ 🐕‍🦺 👉. + +⏮️ 🖼 ✊ ⏮️ 🎙 🎙 📟. + +✋️ 👆 🔜 🤚 🎏 👨‍🎨 🐕‍🦺 ⏮️ 🗒 & 🌅 🎏 🐍 👨‍🎨: + + + +!!! tip + 🚥 👆 ⚙️ 🗒 👆 👨‍🎨, 👆 💪 ⚙️ Pydantic 🗒 📁. + + ⚫️ 📉 👨‍🎨 🐕‍🦺 Pydantic 🏷, ⏮️: + + * 🚘-🛠️ + * 🆎 ✅ + * 🛠️ + * 🔎 + * 🔬 + +## ⚙️ 🏷 + +🔘 🔢, 👆 💪 🔐 🌐 🔢 🏷 🎚 🔗: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="21" + {!> ../../../docs_src/body/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="19" + {!> ../../../docs_src/body/tutorial002_py310.py!} + ``` + +## 📨 💪 ➕ ➡ 🔢 + +👆 💪 📣 ➡ 🔢 & 📨 💪 🎏 🕰. + +**FastAPI** 🔜 🤔 👈 🔢 🔢 👈 🏏 ➡ 🔢 🔜 **✊ ⚪️➡️ ➡**, & 👈 🔢 🔢 👈 📣 Pydantic 🏷 🔜 **✊ ⚪️➡️ 📨 💪**. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="17-18" + {!> ../../../docs_src/body/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="15-16" + {!> ../../../docs_src/body/tutorial003_py310.py!} + ``` + +## 📨 💪 ➕ ➡ ➕ 🔢 🔢 + +👆 💪 📣 **💪**, **➡** & **🔢** 🔢, 🌐 🎏 🕰. + +**FastAPI** 🔜 🤔 🔠 👫 & ✊ 📊 ⚪️➡️ ☑ 🥉. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/body/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="16" + {!> ../../../docs_src/body/tutorial004_py310.py!} + ``` + +🔢 🔢 🔜 🤔 ⏩: + +* 🚥 🔢 📣 **➡**, ⚫️ 🔜 ⚙️ ➡ 🔢. +* 🚥 🔢 **⭐ 🆎** (💖 `int`, `float`, `str`, `bool`, ♒️) ⚫️ 🔜 🔬 **🔢** 🔢. +* 🚥 🔢 📣 🆎 **Pydantic 🏷**, ⚫️ 🔜 🔬 📨 **💪**. + +!!! note + FastAPI 🔜 💭 👈 💲 `q` 🚫 ✔ ↩️ 🔢 💲 `= None`. + + `Union` `Union[str, None]` 🚫 ⚙️ FastAPI, ✋️ 🔜 ✔ 👆 👨‍🎨 🤝 👆 👍 🐕‍🦺 & 🔍 ❌. + +## 🍵 Pydantic + +🚥 👆 🚫 💚 ⚙️ Pydantic 🏷, 👆 💪 ⚙️ **💪** 🔢. 👀 🩺 [💪 - 💗 🔢: ⭐ 💲 💪](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. diff --git a/docs/em/docs/tutorial/cookie-params.md b/docs/em/docs/tutorial/cookie-params.md new file mode 100644 index 0000000000..47f4a62f5c --- /dev/null +++ b/docs/em/docs/tutorial/cookie-params.md @@ -0,0 +1,49 @@ +# 🍪 🔢 + +👆 💪 🔬 🍪 🔢 🎏 🌌 👆 🔬 `Query` & `Path` 🔢. + +## 🗄 `Cookie` + +🥇 🗄 `Cookie`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3" + {!> ../../../docs_src/cookie_params/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/cookie_params/tutorial001_py310.py!} + ``` + +## 📣 `Cookie` 🔢 + +⤴️ 📣 🍪 🔢 ⚙️ 🎏 📊 ⏮️ `Path` & `Query`. + +🥇 💲 🔢 💲, 👆 💪 🚶‍♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/cookie_params/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/cookie_params/tutorial001_py310.py!} + ``` + +!!! note "📡 ℹ" + `Cookie` "👭" 🎓 `Path` & `Query`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓. + + ✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `Cookie` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. + +!!! info + 📣 🍪, 👆 💪 ⚙️ `Cookie`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢. + +## 🌃 + +📣 🍪 ⏮️ `Cookie`, ⚙️ 🎏 ⚠ ⚓ `Query` & `Path`. diff --git a/docs/em/docs/tutorial/cors.md b/docs/em/docs/tutorial/cors.md new file mode 100644 index 0000000000..8c5e33ed7b --- /dev/null +++ b/docs/em/docs/tutorial/cors.md @@ -0,0 +1,84 @@ +# ⚜ (✖️-🇨🇳 ℹ 🤝) + +⚜ ⚖️ "✖️-🇨🇳 ℹ 🤝" 🔗 ⚠ 🕐❔ 🕸 🏃‍♂ 🖥 ✔️ 🕸 📟 👈 🔗 ⏮️ 👩‍💻, & 👩‍💻 🎏 "🇨🇳" 🌘 🕸. + +## 🇨🇳 + +🇨🇳 🌀 🛠️ (`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`). + +⤴️, 🖥 🔜 📨 🇺🇸🔍 `OPTIONS` 📨 👩‍💻, & 🚥 👩‍💻 📨 ☑ 🎚 ✔ 📻 ⚪️➡️ 👉 🎏 🇨🇳 (`http://localhost:8080`) ⤴️ 🖥 🔜 ➡️ 🕸 🕸 📨 🚮 📨 👩‍💻. + +🏆 👉, 👩‍💻 🔜 ✔️ 📇 "✔ 🇨🇳". + +👉 💼, ⚫️ 🔜 ✔️ 🔌 `http://localhost:8080` 🕸 👷 ☑. + +## 🃏 + +⚫️ 💪 📣 📇 `"*"` ("🃏") 💬 👈 🌐 ✔. + +✋️ 👈 🔜 🕴 ✔ 🎯 🆎 📻, 🚫 🌐 👈 🔌 🎓: 🍪, ✔ 🎚 💖 📚 ⚙️ ⏮️ 📨 🤝, ♒️. + +, 🌐 👷 ☑, ⚫️ 👻 ✔ 🎯 ✔ 🇨🇳. + +## ⚙️ `CORSMiddleware` + +👆 💪 🔗 ⚫️ 👆 **FastAPI** 🈸 ⚙️ `CORSMiddleware`. + +* 🗄 `CORSMiddleware`. +* ✍ 📇 ✔ 🇨🇳 (🎻). +* 🚮 ⚫️ "🛠️" 👆 **FastAPI** 🈸. + +👆 💪 ✔ 🚥 👆 👩‍💻 ✔: + +* 🎓 (✔ 🎚, 🍪, ♒️). +* 🎯 🇺🇸🔍 👩‍🔬 (`POST`, `PUT`) ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`. +* 🎯 🇺🇸🔍 🎚 ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`. + +```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` - 📇 🇺🇸🔍 👩‍🔬 👈 🔜 ✔ ✖️-🇨🇳 📨. 🔢 `['GET']`. 👆 💪 ⚙️ `['*']` ✔ 🌐 🐩 👩‍🔬. +* `allow_headers` - 📇 🇺🇸🔍 📨 🎚 👈 🔜 🐕‍🦺 ✖️-🇨🇳 📨. 🔢 `[]`. 👆 💪 ⚙️ `['*']` ✔ 🌐 🎚. `Accept`, `Accept-Language`, `Content-Language` & `Content-Type` 🎚 🕧 ✔ 🙅 ⚜ 📨. +* `allow_credentials` - 🎦 👈 🍪 🔜 🐕‍🦺 ✖️-🇨🇳 📨. 🔢 `False`. , `allow_origins` 🚫🔜 ⚒ `['*']` 🎓 ✔, 🇨🇳 🔜 ✔. +* `expose_headers` - 🎦 🙆 📨 🎚 👈 🔜 ⚒ ♿ 🖥. 🔢 `[]`. +* `max_age` - ⚒ 🔆 🕰 🥈 🖥 💾 ⚜ 📨. 🔢 `600`. + +🛠️ 📨 2️⃣ 🎯 🆎 🇺🇸🔍 📨... + +### ⚜ 🛫 📨 + +👉 🙆 `OPTIONS` 📨 ⏮️ `Origin` & `Access-Control-Request-Method` 🎚. + +👉 💼 🛠️ 🔜 🆘 📨 📨 & 📨 ⏮️ ☑ ⚜ 🎚, & 👯‍♂️ `200` ⚖️ `400` 📨 🎓 🎯. + +### 🙅 📨 + +🙆 📨 ⏮️ `Origin` 🎚. 👉 💼 🛠️ 🔜 🚶‍♀️ 📨 🔘 😐, ✋️ 🔜 🔌 ☑ ⚜ 🎚 🔛 📨. + +## 🌅 ℹ + +🌖 ℹ 🔃 , ✅ 🦎 ⚜ 🧾. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.middleware.cors import CORSMiddleware`. + + **FastAPI** 🚚 📚 🛠️ `fastapi.middleware` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 🛠️ 👟 🔗 ⚪️➡️ 💃. diff --git a/docs/em/docs/tutorial/debugging.md b/docs/em/docs/tutorial/debugging.md new file mode 100644 index 0000000000..c7c11b5cec --- /dev/null +++ b/docs/em/docs/tutorial/debugging.md @@ -0,0 +1,112 @@ +# 🛠️ + +👆 💪 🔗 🕹 👆 👨‍🎨, 🖼 ⏮️ 🎙 🎙 📟 ⚖️ 🗒. + +## 🤙 `uvicorn` + +👆 FastAPI 🈸, 🗄 & 🏃 `uvicorn` 🔗: + +```Python hl_lines="1 15" +{!../../../docs_src/debugging/tutorial001.py!} +``` + +### 🔃 `__name__ == "__main__"` + +👑 🎯 `__name__ == "__main__"` ✔️ 📟 👈 🛠️ 🕐❔ 👆 📁 🤙 ⏮️: + +
+ +```console +$ python myapp.py +``` + +
+ +✋️ 🚫 🤙 🕐❔ ➕1️⃣ 📁 🗄 ⚫️, 💖: + +```Python +from myapp import app +``` + +#### 🌅 ℹ + +➡️ 💬 👆 📁 🌟 `myapp.py`. + +🚥 👆 🏃 ⚫️ ⏮️: + +
+ +```console +$ python myapp.py +``` + +
+ +⤴️ 🔗 🔢 `__name__` 👆 📁, ✍ 🔁 🐍, 🔜 ✔️ 💲 🎻 `"__main__"`. + +, 📄: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +🔜 🏃. + +--- + +👉 🏆 🚫 🔨 🚥 👆 🗄 👈 🕹 (📁). + +, 🚥 👆 ✔️ ➕1️⃣ 📁 `importer.py` ⏮️: + +```Python +from myapp import app + +# Some more code +``` + +👈 💼, 🏧 🔢 🔘 `myapp.py` 🔜 🚫 ✔️ 🔢 `__name__` ⏮️ 💲 `"__main__"`. + +, ⏸: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +🔜 🚫 🛠️. + +!!! info + 🌅 ℹ, ✅ 🛂 🐍 🩺. + +## 🏃 👆 📟 ⏮️ 👆 🕹 + +↩️ 👆 🏃 Uvicorn 💽 🔗 ⚪️➡️ 👆 📟, 👆 💪 🤙 👆 🐍 📋 (👆 FastAPI 🈸) 🔗 ⚪️➡️ 🕹. + +--- + +🖼, 🎙 🎙 📟, 👆 💪: + +* 🚶 "ℹ" 🎛. +* "🚮 📳...". +* 🖊 "🐍" +* 🏃 🕹 ⏮️ 🎛 "`Python: Current File (Integrated Terminal)`". + +⚫️ 🔜 ⤴️ ▶️ 💽 ⏮️ 👆 **FastAPI** 📟, ⛔️ 👆 0️⃣, ♒️. + +📥 ❔ ⚫️ 💪 👀: + + + +--- + +🚥 👆 ⚙️ 🗒, 👆 💪: + +* 📂 "🏃" 🍣. +* 🖊 🎛 "ℹ...". +* ⤴️ 🔑 🍣 🎦 🆙. +* 🖊 📁 ℹ (👉 💼, `main.py`). + +⚫️ 🔜 ⤴️ ▶️ 💽 ⏮️ 👆 **FastAPI** 📟, ⛔️ 👆 0️⃣, ♒️. + +📥 ❔ ⚫️ 💪 👀: + + diff --git a/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md new file mode 100644 index 0000000000..e2d2686d34 --- /dev/null +++ b/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md @@ -0,0 +1,247 @@ +# 🎓 🔗 + +⏭ 🤿 ⏬ 🔘 **🔗 💉** ⚙️, ➡️ ♻ ⏮️ 🖼. + +## `dict` ⚪️➡️ ⏮️ 🖼 + +⏮️ 🖼, 👥 🛬 `dict` ⚪️➡️ 👆 🔗 ("☑"): + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +✋️ ⤴️ 👥 🤚 `dict` 🔢 `commons` *➡ 🛠️ 🔢*. + +& 👥 💭 👈 👨‍🎨 💪 🚫 🚚 📚 🐕‍🦺 (💖 🛠️) `dict`Ⓜ, ↩️ 👫 💪 🚫 💭 👫 🔑 & 💲 🆎. + +👥 💪 👍... + +## ⚫️❔ ⚒ 🔗 + +🆙 🔜 👆 ✔️ 👀 🔗 📣 🔢. + +✋️ 👈 🚫 🕴 🌌 📣 🔗 (👐 ⚫️ 🔜 🎲 🌖 ⚠). + +🔑 ⚖ 👈 🔗 🔜 "🇧🇲". + +"**🇧🇲**" 🐍 🕳 👈 🐍 💪 "🤙" 💖 🔢. + +, 🚥 👆 ✔️ 🎚 `something` (👈 💪 _🚫_ 🔢) & 👆 💪 "🤙" ⚫️ (🛠️ ⚫️) 💖: + +```Python +something() +``` + +⚖️ + +```Python +something(some_argument, some_keyword_argument="foo") +``` + +⤴️ ⚫️ "🇧🇲". + +## 🎓 🔗 + +👆 5️⃣📆 👀 👈 ✍ 👐 🐍 🎓, 👆 ⚙️ 👈 🎏 ❕. + +🖼: + +```Python +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +👉 💼, `fluffy` 👐 🎓 `Cat`. + +& ✍ `fluffy`, 👆 "🤙" `Cat`. + +, 🐍 🎓 **🇧🇲**. + +⤴️, **FastAPI**, 👆 💪 ⚙️ 🐍 🎓 🔗. + +⚫️❔ FastAPI 🤙 ✅ 👈 ⚫️ "🇧🇲" (🔢, 🎓 ⚖️ 🕳 🙆) & 🔢 🔬. + +🚥 👆 🚶‍♀️ "🇧🇲" 🔗 **FastAPI**, ⚫️ 🔜 🔬 🔢 👈 "🇧🇲", & 🛠️ 👫 🎏 🌌 🔢 *➡ 🛠️ 🔢*. ✅ 🎧-🔗. + +👈 ✔ 🇧🇲 ⏮️ 🙅‍♂ 🔢 🌐. 🎏 ⚫️ 🔜 *➡ 🛠️ 🔢* ⏮️ 🙅‍♂ 🔢. + +⤴️, 👥 💪 🔀 🔗 "☑" `common_parameters` ⚪️➡️ 🔛 🎓 `CommonQueryParams`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="11-15" + {!> ../../../docs_src/dependencies/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="9-13" + {!> ../../../docs_src/dependencies/tutorial002_py310.py!} + ``` + +💸 🙋 `__init__` 👩‍🔬 ⚙️ ✍ 👐 🎓: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="12" + {!> ../../../docs_src/dependencies/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/dependencies/tutorial002_py310.py!} + ``` + +...⚫️ ✔️ 🎏 🔢 👆 ⏮️ `common_parameters`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="6" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +📚 🔢 ⚫️❔ **FastAPI** 🔜 ⚙️ "❎" 🔗. + +👯‍♂️ 💼, ⚫️ 🔜 ✔️: + +* 📦 `q` 🔢 🔢 👈 `str`. +* `skip` 🔢 🔢 👈 `int`, ⏮️ 🔢 `0`. +* `limit` 🔢 🔢 👈 `int`, ⏮️ 🔢 `100`. + +👯‍♂️ 💼 💽 🔜 🗜, ✔, 📄 🔛 🗄 🔗, ♒️. + +## ⚙️ ⚫️ + +🔜 👆 💪 📣 👆 🔗 ⚙️ 👉 🎓. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial002_py310.py!} + ``` + +**FastAPI** 🤙 `CommonQueryParams` 🎓. 👉 ✍ "👐" 👈 🎓 & 👐 🔜 🚶‍♀️ 🔢 `commons` 👆 🔢. + +## 🆎 ✍ 🆚 `Depends` + +👀 ❔ 👥 ✍ `CommonQueryParams` 🕐 🔛 📟: + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +🏁 `CommonQueryParams`,: + +```Python +... = Depends(CommonQueryParams) +``` + +...⚫️❔ **FastAPI** 🔜 🤙 ⚙️ 💭 ⚫️❔ 🔗. + +⚪️➡️ ⚫️ 👈 FastAPI 🔜 ⚗ 📣 🔢 & 👈 ⚫️❔ FastAPI 🔜 🤙 🤙. + +--- + +👉 💼, 🥇 `CommonQueryParams`,: + +```Python +commons: CommonQueryParams ... +``` + +...🚫 ✔️ 🙆 🎁 🔑 **FastAPI**. FastAPI 🏆 🚫 ⚙️ ⚫️ 💽 🛠️, 🔬, ♒️. (⚫️ ⚙️ `= Depends(CommonQueryParams)` 👈). + +👆 💪 🤙 ✍: + +```Python +commons = Depends(CommonQueryParams) +``` + +...: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial003_py310.py!} + ``` + +✋️ 📣 🆎 💡 👈 🌌 👆 👨‍🎨 🔜 💭 ⚫️❔ 🔜 🚶‍♀️ 🔢 `commons`, & ⤴️ ⚫️ 💪 ℹ 👆 ⏮️ 📟 🛠️, 🆎 ✅, ♒️: + + + +## ⌨ + +✋️ 👆 👀 👈 👥 ✔️ 📟 🔁 📥, ✍ `CommonQueryParams` 🕐: + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +**FastAPI** 🚚 ⌨ 👫 💼, 🌐❔ 🔗 *🎯* 🎓 👈 **FastAPI** 🔜 "🤙" ✍ 👐 🎓 ⚫️. + +📚 🎯 💼, 👆 💪 📄: + +↩️ ✍: + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +...👆 ✍: + +```Python +commons: CommonQueryParams = Depends() +``` + +👆 📣 🔗 🆎 🔢, & 👆 ⚙️ `Depends()` 🚮 "🔢" 💲 (👈 ⏮️ `=`) 👈 🔢 🔢, 🍵 🙆 🔢 `Depends()`, ↩️ ✔️ ✍ 🌕 🎓 *🔄* 🔘 `Depends(CommonQueryParams)`. + +🎏 🖼 🔜 ⤴️ 👀 💖: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial004_py310.py!} + ``` + +...& **FastAPI** 🔜 💭 ⚫️❔. + +!!! tip + 🚥 👈 😑 🌅 😨 🌘 👍, 🤷‍♂ ⚫️, 👆 🚫 *💪* ⚫️. + + ⚫️ ⌨. ↩️ **FastAPI** 💅 🔃 🤝 👆 📉 📟 🔁. diff --git a/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md new file mode 100644 index 0000000000..4d54b91c77 --- /dev/null +++ b/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -0,0 +1,71 @@ +# 🔗 ➡ 🛠️ 👨‍🎨 + +💼 👆 🚫 🤙 💪 📨 💲 🔗 🔘 👆 *➡ 🛠️ 🔢*. + +⚖️ 🔗 🚫 📨 💲. + +✋️ 👆 💪 ⚫️ 🛠️/❎. + +📚 💼, ↩️ 📣 *➡ 🛠️ 🔢* 🔢 ⏮️ `Depends`, 👆 💪 🚮 `list` `dependencies` *➡ 🛠️ 👨‍🎨*. + +## 🚮 `dependencies` *➡ 🛠️ 👨‍🎨* + +*➡ 🛠️ 👨‍🎨* 📨 📦 ❌ `dependencies`. + +⚫️ 🔜 `list` `Depends()`: + +```Python hl_lines="17" +{!../../../docs_src/dependencies/tutorial006.py!} +``` + +👉 🔗 🔜 🛠️/❎ 🎏 🌌 😐 🔗. ✋️ 👫 💲 (🚥 👫 📨 🙆) 🏆 🚫 🚶‍♀️ 👆 *➡ 🛠️ 🔢*. + +!!! tip + 👨‍🎨 ✅ ♻ 🔢 🔢, & 🎦 👫 ❌. + + ⚙️ 👉 `dependencies` *➡ 🛠️ 👨‍🎨* 👆 💪 ⚒ 💭 👫 🛠️ ⏪ ❎ 👨‍🎨/🏭 ❌. + + ⚫️ 💪 ℹ ❎ 😨 🆕 👩‍💻 👈 👀 ♻ 🔢 👆 📟 & 💪 💭 ⚫️ 🙃. + +!!! info + 👉 🖼 👥 ⚙️ 💭 🛃 🎚 `X-Key` & `X-Token`. + + ✋️ 🎰 💼, 🕐❔ 🛠️ 💂‍♂, 👆 🔜 🤚 🌖 💰 ⚪️➡️ ⚙️ 🛠️ [💂‍♂ 🚙 (⏭ 📃)](../security/index.md){.internal-link target=_blank}. + +## 🔗 ❌ & 📨 💲 + +👆 💪 ⚙️ 🎏 🔗 *🔢* 👆 ⚙️ 🛎. + +### 🔗 📄 + +👫 💪 📣 📨 📄 (💖 🎚) ⚖️ 🎏 🎧-🔗: + +```Python hl_lines="6 11" +{!../../../docs_src/dependencies/tutorial006.py!} +``` + +### 🤚 ⚠ + +👫 🔗 💪 `raise` ⚠, 🎏 😐 🔗: + +```Python hl_lines="8 13" +{!../../../docs_src/dependencies/tutorial006.py!} +``` + +### 📨 💲 + +& 👫 💪 📨 💲 ⚖️ 🚫, 💲 🏆 🚫 ⚙️. + +, 👆 💪 🏤-⚙️ 😐 🔗 (👈 📨 💲) 👆 ⏪ ⚙️ 👱 🙆, & ✋️ 💲 🏆 🚫 ⚙️, 🔗 🔜 🛠️: + +```Python hl_lines="9 14" +{!../../../docs_src/dependencies/tutorial006.py!} +``` + +## 🔗 👪 *➡ 🛠️* + +⏪, 🕐❔ 👂 🔃 ❔ 📊 🦏 🈸 ([🦏 🈸 - 💗 📁](../../tutorial/bigger-applications.md){.internal-link target=_blank}), 🎲 ⏮️ 💗 📁, 👆 🔜 💡 ❔ 📣 👁 `dependencies` 🔢 👪 *➡ 🛠️*. + +## 🌐 🔗 + +⏭ 👥 🔜 👀 ❔ 🚮 🔗 🎂 `FastAPI` 🈸, 👈 👫 ✔ 🔠 *➡ 🛠️*. diff --git a/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 0000000000..9617667f43 --- /dev/null +++ b/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,219 @@ +# 🔗 ⏮️ 🌾 + +FastAPI 🐕‍🦺 🔗 👈 ➕ 🔁 ⏮️ 🏁. + +👉, ⚙️ `yield` ↩️ `return`, & ✍ ➕ 🔁 ⏮️. + +!!! tip + ⚒ 💭 ⚙️ `yield` 1️⃣ 👁 🕰. + +!!! note "📡 ℹ" + 🙆 🔢 👈 ☑ ⚙️ ⏮️: + + * `@contextlib.contextmanager` ⚖️ + * `@contextlib.asynccontextmanager` + + 🔜 ☑ ⚙️ **FastAPI** 🔗. + + 👐, FastAPI ⚙️ 📚 2️⃣ 👨‍🎨 🔘. + +## 💽 🔗 ⏮️ `yield` + +🖼, 👆 💪 ⚙️ 👉 ✍ 💽 🎉 & 🔐 ⚫️ ⏮️ 🏁. + +🕴 📟 ⏭ & 🔌 `yield` 📄 🛠️ ⏭ 📨 📨: + +```Python hl_lines="2-4" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +🌾 💲 ⚫️❔ 💉 🔘 *➡ 🛠️* & 🎏 🔗: + +```Python hl_lines="4" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +📟 📄 `yield` 📄 🛠️ ⏮️ 📨 ✔️ 🚚: + +```Python hl_lines="5-6" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +!!! tip + 👆 💪 ⚙️ `async` ⚖️ 😐 🔢. + + **FastAPI** 🔜 ▶️️ 👜 ⏮️ 🔠, 🎏 ⏮️ 😐 🔗. + +## 🔗 ⏮️ `yield` & `try` + +🚥 👆 ⚙️ `try` 🍫 🔗 ⏮️ `yield`, 👆 🔜 📨 🙆 ⚠ 👈 🚮 🕐❔ ⚙️ 🔗. + +🖼, 🚥 📟 ☝ 🖕, ➕1️⃣ 🔗 ⚖️ *➡ 🛠️*, ⚒ 💽 💵 "💾" ⚖️ ✍ 🙆 🎏 ❌, 👆 🔜 📨 ⚠ 👆 🔗. + +, 👆 💪 👀 👈 🎯 ⚠ 🔘 🔗 ⏮️ `except SomeException`. + +🎏 🌌, 👆 💪 ⚙️ `finally` ⚒ 💭 🚪 📶 🛠️, 🙅‍♂ 🤔 🚥 📤 ⚠ ⚖️ 🚫. + +```Python hl_lines="3 5" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +## 🎧-🔗 ⏮️ `yield` + +👆 💪 ✔️ 🎧-🔗 & "🌲" 🎧-🔗 🙆 📐 & 💠, & 🙆 ⚖️ 🌐 👫 💪 ⚙️ `yield`. + +**FastAPI** 🔜 ⚒ 💭 👈 "🚪 📟" 🔠 🔗 ⏮️ `yield` 🏃 ☑ ✔. + +🖼, `dependency_c` 💪 ✔️ 🔗 🔛 `dependency_b`, & `dependency_b` 🔛 `dependency_a`: + +```Python hl_lines="4 12 20" +{!../../../docs_src/dependencies/tutorial008.py!} +``` + +& 🌐 👫 💪 ⚙️ `yield`. + +👉 💼 `dependency_c`, 🛠️ 🚮 🚪 📟, 💪 💲 ⚪️➡️ `dependency_b` (📥 📛 `dep_b`) 💪. + +& , 🔄, `dependency_b` 💪 💲 ⚪️➡️ `dependency_a` (📥 📛 `dep_a`) 💪 🚮 🚪 📟. + +```Python hl_lines="16-17 24-25" +{!../../../docs_src/dependencies/tutorial008.py!} +``` + +🎏 🌌, 👆 💪 ✔️ 🔗 ⏮️ `yield` & `return` 🌀. + +& 👆 💪 ✔️ 👁 🔗 👈 🚚 📚 🎏 🔗 ⏮️ `yield`, ♒️. + +👆 💪 ✔️ 🙆 🌀 🔗 👈 👆 💚. + +**FastAPI** 🔜 ⚒ 💭 🌐 🏃 ☑ ✔. + +!!! note "📡 ℹ" + 👉 👷 👏 🐍 🔑 👨‍💼. + + **FastAPI** ⚙️ 👫 🔘 🏆 👉. + +## 🔗 ⏮️ `yield` & `HTTPException` + +👆 👀 👈 👆 💪 ⚙️ 🔗 ⏮️ `yield` & ✔️ `try` 🍫 👈 ✊ ⚠. + +⚫️ 5️⃣📆 😋 🤚 `HTTPException` ⚖️ 🎏 🚪 📟, ⏮️ `yield`. ✋️ **⚫️ 🏆 🚫 👷**. + +🚪 📟 🔗 ⏮️ `yield` 🛠️ *⏮️* 📨 📨, [⚠ 🐕‍🦺](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} 🔜 ✔️ ⏪ 🏃. 📤 🕳 😽 ⚠ 🚮 👆 🔗 🚪 📟 (⏮️ `yield`). + +, 🚥 👆 🤚 `HTTPException` ⏮️ `yield`, 🔢 (⚖️ 🙆 🛃) ⚠ 🐕‍🦺 👈 ✊ `HTTPException`Ⓜ & 📨 🇺🇸🔍 4️⃣0️⃣0️⃣ 📨 🏆 🚫 📤 ✊ 👈 ⚠ 🚫🔜. + +👉 ⚫️❔ ✔ 🕳 ⚒ 🔗 (✅ 💽 🎉), 🖼, ⚙️ 🖥 📋. + +🖥 📋 🏃 *⏮️* 📨 ✔️ 📨. 📤 🙅‍♂ 🌌 🤚 `HTTPException` ↩️ 📤 🚫 🌌 🔀 📨 👈 *⏪ 📨*. + +✋️ 🚥 🖥 📋 ✍ 💽 ❌, 🌘 👆 💪 💾 ⚖️ 😬 🔐 🎉 🔗 ⏮️ `yield`, & 🎲 🕹 ❌ ⚖️ 📄 ⚫️ 🛰 🕵 ⚙️. + +🚥 👆 ✔️ 📟 👈 👆 💭 💪 🤚 ⚠, 🏆 😐/"🙃" 👜 & 🚮 `try` 🍫 👈 📄 📟. + +🚥 👆 ✔️ 🛃 ⚠ 👈 👆 🔜 💖 🍵 *⏭* 🛬 📨 & 🎲 ❎ 📨, 🎲 🙋‍♀ `HTTPException`, ✍ [🛃 ⚠ 🐕‍🦺](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. + +!!! tip + 👆 💪 🤚 ⚠ 🔌 `HTTPException` *⏭* `yield`. ✋️ 🚫 ⏮️. + +🔁 🛠️ 🌅 ⚖️ 🌘 💖 👉 📊. 🕰 💧 ⚪️➡️ 🔝 🔝. & 🔠 🏓 1️⃣ 🍕 🔗 ⚖️ 🛠️ 📟. + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,tasks: Can raise exception for dependency, handled after response is sent + Note over client,operation: Can raise HTTPException and can change the response + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise + dep -->> handler: Raise HTTPException + handler -->> client: HTTP error response + dep -->> dep: Raise other exception + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise HTTPException + dep -->> handler: Auto forward exception + handler -->> client: HTTP error response + operation -->> dep: Raise other exception + dep -->> handler: Auto forward exception + end + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> dep: Raise other exception + end + Note over dep: After yield + opt Handle other exception + dep -->> dep: Handle exception, can't change response. E.g. close DB session. + end +``` + +!!! info + 🕴 **1️⃣ 📨** 🔜 📨 👩‍💻. ⚫️ 💪 1️⃣ ❌ 📨 ⚖️ ⚫️ 🔜 📨 ⚪️➡️ *➡ 🛠️*. + + ⏮️ 1️⃣ 📚 📨 📨, 🙅‍♂ 🎏 📨 💪 📨. + +!!! tip + 👉 📊 🎦 `HTTPException`, ✋️ 👆 💪 🤚 🙆 🎏 ⚠ ❔ 👆 ✍ [🛃 ⚠ 🐕‍🦺](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. + + 🚥 👆 🤚 🙆 ⚠, ⚫️ 🔜 🚶‍♀️ 🔗 ⏮️ 🌾, 🔌 `HTTPException`, & ⤴️ **🔄** ⚠ 🐕‍🦺. 🚥 📤 🙅‍♂ ⚠ 🐕‍🦺 👈 ⚠, ⚫️ 🔜 ⤴️ 🍵 🔢 🔗 `ServerErrorMiddleware`, 🛬 5️⃣0️⃣0️⃣ 🇺🇸🔍 👔 📟, ➡️ 👩‍💻 💭 👈 📤 ❌ 💽. + +## 🔑 👨‍💼 + +### ⚫️❔ "🔑 👨‍💼" + +"🔑 👨‍💼" 🙆 👈 🐍 🎚 👈 👆 💪 ⚙️ `with` 📄. + +🖼, 👆 💪 ⚙️ `with` ✍ 📁: + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +🔘, `open("./somefile.txt")` ✍ 🎚 👈 🤙 "🔑 👨‍💼". + +🕐❔ `with` 🍫 🏁, ⚫️ ⚒ 💭 🔐 📁, 🚥 📤 ⚠. + +🕐❔ 👆 ✍ 🔗 ⏮️ `yield`, **FastAPI** 🔜 🔘 🗜 ⚫️ 🔑 👨‍💼, & 🌀 ⚫️ ⏮️ 🎏 🔗 🧰. + +### ⚙️ 🔑 👨‍💼 🔗 ⏮️ `yield` + +!!! warning + 👉, 🌅 ⚖️ 🌘, "🏧" 💭. + + 🚥 👆 ▶️ ⏮️ **FastAPI** 👆 💪 💚 🚶 ⚫️ 🔜. + +🐍, 👆 💪 ✍ 🔑 👨‍💼 🏗 🎓 ⏮️ 2️⃣ 👩‍🔬: `__enter__()` & `__exit__()`. + +👆 💪 ⚙️ 👫 🔘 **FastAPI** 🔗 ⏮️ `yield` ⚙️ +`with` ⚖️ `async with` 📄 🔘 🔗 🔢: + +```Python hl_lines="1-9 13" +{!../../../docs_src/dependencies/tutorial010.py!} +``` + +!!! tip + ➕1️⃣ 🌌 ✍ 🔑 👨‍💼 ⏮️: + + * `@contextlib.contextmanager` ⚖️ + * `@contextlib.asynccontextmanager` + + ⚙️ 👫 🎀 🔢 ⏮️ 👁 `yield`. + + 👈 ⚫️❔ **FastAPI** ⚙️ 🔘 🔗 ⏮️ `yield`. + + ✋️ 👆 🚫 ✔️ ⚙️ 👨‍🎨 FastAPI 🔗 (& 👆 🚫🔜 🚫). + + FastAPI 🔜 ⚫️ 👆 🔘. diff --git a/docs/em/docs/tutorial/dependencies/global-dependencies.md b/docs/em/docs/tutorial/dependencies/global-dependencies.md new file mode 100644 index 0000000000..81759d0e83 --- /dev/null +++ b/docs/em/docs/tutorial/dependencies/global-dependencies.md @@ -0,0 +1,17 @@ +# 🌐 🔗 + +🆎 🈸 👆 💪 💚 🚮 🔗 🎂 🈸. + +🎏 🌌 👆 💪 [🚮 `dependencies` *➡ 🛠️ 👨‍🎨*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, 👆 💪 🚮 👫 `FastAPI` 🈸. + +👈 💼, 👫 🔜 ✔ 🌐 *➡ 🛠️* 🈸: + +```Python hl_lines="15" +{!../../../docs_src/dependencies/tutorial012.py!} +``` + +& 🌐 💭 📄 🔃 [❎ `dependencies` *➡ 🛠️ 👨‍🎨*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} ✔, ✋️ 👉 💼, 🌐 *➡ 🛠️* 📱. + +## 🔗 👪 *➡ 🛠️* + +⏪, 🕐❔ 👂 🔃 ❔ 📊 🦏 🈸 ([🦏 🈸 - 💗 📁](../../tutorial/bigger-applications.md){.internal-link target=_blank}), 🎲 ⏮️ 💗 📁, 👆 🔜 💡 ❔ 📣 👁 `dependencies` 🔢 👪 *➡ 🛠️*. diff --git a/docs/em/docs/tutorial/dependencies/index.md b/docs/em/docs/tutorial/dependencies/index.md new file mode 100644 index 0000000000..ffd38d7168 --- /dev/null +++ b/docs/em/docs/tutorial/dependencies/index.md @@ -0,0 +1,233 @@ +# 🔗 + +**FastAPI** ✔️ 📶 🏋️ ✋️ 🏋️ **🔗 💉** ⚙️. + +⚫️ 🏗 📶 🙅 ⚙️, & ⚒ ⚫️ 📶 ⏩ 🙆 👩‍💻 🛠️ 🎏 🦲 ⏮️ **FastAPI**. + +## ⚫️❔ "🔗 💉" + +**"🔗 💉"** ⛓, 📋, 👈 📤 🌌 👆 📟 (👉 💼, 👆 *➡ 🛠️ 🔢*) 📣 👜 👈 ⚫️ 🚚 👷 & ⚙️: "🔗". + +& ⤴️, 👈 ⚙️ (👉 💼 **FastAPI**) 🔜 ✊ 💅 🔨 ⚫️❔ 💪 🚚 👆 📟 ⏮️ 📚 💪 🔗 ("💉" 🔗). + +👉 📶 ⚠ 🕐❔ 👆 💪: + +* ✔️ 💰 ⚛ (🎏 📟 ⚛ 🔄 & 🔄). +* 💰 💽 🔗. +* 🛠️ 💂‍♂, 🤝, 🔑 📄, ♒️. +* & 📚 🎏 👜... + +🌐 👫, ⏪ 📉 📟 🔁. + +## 🥇 🔁 + +➡️ 👀 📶 🙅 🖼. ⚫️ 🔜 🙅 👈 ⚫️ 🚫 📶 ⚠, 🔜. + +✋️ 👉 🌌 👥 💪 🎯 🔛 ❔ **🔗 💉** ⚙️ 👷. + +### ✍ 🔗, ⚖️ "☑" + +➡️ 🥇 🎯 🔛 🔗. + +⚫️ 🔢 👈 💪 ✊ 🌐 🎏 🔢 👈 *➡ 🛠️ 🔢* 💪 ✊: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="8-11" + {!> ../../../docs_src/dependencies/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="6-7" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +👈 ⚫️. + +**2️⃣ ⏸**. + +& ⚫️ ✔️ 🎏 💠 & 📊 👈 🌐 👆 *➡ 🛠️ 🔢* ✔️. + +👆 💪 💭 ⚫️ *➡ 🛠️ 🔢* 🍵 "👨‍🎨" (🍵 `@app.get("/some-path")`). + +& ⚫️ 💪 📨 🕳 👆 💚. + +👉 💼, 👉 🔗 ⌛: + +* 📦 🔢 🔢 `q` 👈 `str`. +* 📦 🔢 🔢 `skip` 👈 `int`, & 🔢 `0`. +* 📦 🔢 🔢 `limit` 👈 `int`, & 🔢 `100`. + +& ⤴️ ⚫️ 📨 `dict` ⚗ 📚 💲. + +### 🗄 `Depends` + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3" + {!> ../../../docs_src/dependencies/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +### 📣 🔗, "⚓️" + +🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️. ⏮️ 👆 *➡ 🛠️ 🔢* 🔢, ⚙️ `Depends` ⏮️ 🆕 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="15 20" + {!> ../../../docs_src/dependencies/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="11 16" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +👐 👆 ⚙️ `Depends` 🔢 👆 🔢 🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️, `Depends` 👷 👄 🎏. + +👆 🕴 🤝 `Depends` 👁 🔢. + +👉 🔢 🔜 🕳 💖 🔢. + +& 👈 🔢 ✊ 🔢 🎏 🌌 👈 *➡ 🛠️ 🔢* . + +!!! tip + 👆 🔜 👀 ⚫️❔ 🎏 "👜", ↖️ ⚪️➡️ 🔢, 💪 ⚙️ 🔗 ⏭ 📃. + +🕐❔ 🆕 📨 🛬, **FastAPI** 🔜 ✊ 💅: + +* 🤙 👆 🔗 ("☑") 🔢 ⏮️ ☑ 🔢. +* 🤚 🏁 ⚪️➡️ 👆 🔢. +* 🛠️ 👈 🏁 🔢 👆 *➡ 🛠️ 🔢*. + +```mermaid +graph TB + +common_parameters(["common_parameters"]) +read_items["/items/"] +read_users["/users/"] + +common_parameters --> read_items +common_parameters --> read_users +``` + +👉 🌌 👆 ✍ 🔗 📟 🕐 & **FastAPI** ✊ 💅 🤙 ⚫️ 👆 *➡ 🛠️*. + +!!! check + 👀 👈 👆 🚫 ✔️ ✍ 🎁 🎓 & 🚶‍♀️ ⚫️ 👱 **FastAPI** "®" ⚫️ ⚖️ 🕳 🎏. + + 👆 🚶‍♀️ ⚫️ `Depends` & **FastAPI** 💭 ❔ 🎂. + +## `async` ⚖️ 🚫 `async` + +🔗 🔜 🤙 **FastAPI** (🎏 👆 *➡ 🛠️ 🔢*), 🎏 🚫 ✔ ⏪ 🔬 👆 🔢. + +👆 💪 ⚙️ `async def` ⚖️ 😐 `def`. + +& 👆 💪 📣 🔗 ⏮️ `async def` 🔘 😐 `def` *➡ 🛠️ 🔢*, ⚖️ `def` 🔗 🔘 `async def` *➡ 🛠️ 🔢*, ♒️. + +⚫️ 🚫 🤔. **FastAPI** 🔜 💭 ⚫️❔. + +!!! note + 🚥 👆 🚫 💭, ✅ [🔁: *"🏃 ❓" *](../../async.md){.internal-link target=_blank} 📄 🔃 `async` & `await` 🩺. + +## 🛠️ ⏮️ 🗄 + +🌐 📨 📄, 🔬 & 📄 👆 🔗 (& 🎧-🔗) 🔜 🛠️ 🎏 🗄 🔗. + +, 🎓 🩺 🔜 ✔️ 🌐 ℹ ⚪️➡️ 👫 🔗 💁‍♂️: + + + +## 🙅 ⚙️ + +🚥 👆 👀 ⚫️, *➡ 🛠️ 🔢* 📣 ⚙️ 🕐❔ *➡* & *🛠️* 🏏, & ⤴️ **FastAPI** ✊ 💅 🤙 🔢 ⏮️ ☑ 🔢, ❎ 📊 ⚪️➡️ 📨. + +🤙, 🌐 (⚖️ 🏆) 🕸 🛠️ 👷 👉 🎏 🌌. + +👆 🙅 🤙 👈 🔢 🔗. 👫 🤙 👆 🛠️ (👉 💼, **FastAPI**). + +⏮️ 🔗 💉 ⚙️, 👆 💪 💬 **FastAPI** 👈 👆 *➡ 🛠️ 🔢* "🪀" 🔛 🕳 🙆 👈 🔜 🛠️ ⏭ 👆 *➡ 🛠️ 🔢*, & **FastAPI** 🔜 ✊ 💅 🛠️ ⚫️ & "💉" 🏁. + +🎏 ⚠ ⚖ 👉 🎏 💭 "🔗 💉": + +* ℹ +* 🐕‍🦺 +* 🐕‍🦺 +* 💉 +* 🦲 + +## **FastAPI** 🔌-🔌 + +🛠️ & "🔌-"Ⓜ 💪 🏗 ⚙️ **🔗 💉** ⚙️. ✋️ 👐, 📤 🤙 **🙅‍♂ 💪 ✍ "🔌-🔌"**, ⚙️ 🔗 ⚫️ 💪 📣 ♾ 🔢 🛠️ & 🔗 👈 ▶️️ 💪 👆 *➡ 🛠️ 🔢*. + +& 🔗 💪 ✍ 📶 🙅 & 🏋️ 🌌 👈 ✔ 👆 🗄 🐍 📦 👆 💪, & 🛠️ 👫 ⏮️ 👆 🛠️ 🔢 👩‍❤‍👨 ⏸ 📟, *🌖*. + +👆 🔜 👀 🖼 👉 ⏭ 📃, 🔃 🔗 & ☁ 💽, 💂‍♂, ♒️. + +## **FastAPI** 🔗 + +🦁 🔗 💉 ⚙️ ⚒ **FastAPI** 🔗 ⏮️: + +* 🌐 🔗 💽 +* ☁ 💽 +* 🔢 📦 +* 🔢 🔗 +* 🤝 & ✔ ⚙️ +* 🛠️ ⚙️ ⚖ ⚙️ +* 📨 💽 💉 ⚙️ +* ♒️. + +## 🙅 & 🏋️ + +👐 🔗 🔗 💉 ⚙️ 📶 🙅 🔬 & ⚙️, ⚫️ 📶 🏋️. + +👆 💪 🔬 🔗 👈 🔄 💪 🔬 🔗 👫. + +🔚, 🔗 🌲 🔗 🏗, & **🔗 💉** ⚙️ ✊ 💅 🔬 🌐 👉 🔗 👆 (& 👫 🎧-🔗) & 🚚 (💉) 🏁 🔠 🔁. + +🖼, ➡️ 💬 👆 ✔️ 4️⃣ 🛠️ 🔗 (*➡ 🛠️*): + +* `/items/public/` +* `/items/private/` +* `/users/{user_id}/activate` +* `/items/pro/` + +⤴️ 👆 💪 🚮 🎏 ✔ 📄 🔠 👫 ⏮️ 🔗 & 🎧-🔗: + +```mermaid +graph TB + +current_user(["current_user"]) +active_user(["active_user"]) +admin_user(["admin_user"]) +paying_user(["paying_user"]) + +public["/items/public/"] +private["/items/private/"] +activate_user["/users/{user_id}/activate"] +pro_items["/items/pro/"] + +current_user --> active_user +active_user --> admin_user +active_user --> paying_user + +current_user --> public +active_user --> private +admin_user --> activate_user +paying_user --> pro_items +``` + +## 🛠️ ⏮️ **🗄** + +🌐 👫 🔗, ⏪ 📣 👫 📄, 🚮 🔢, 🔬, ♒️. 👆 *➡ 🛠️*. + +**FastAPI** 🔜 ✊ 💅 🚮 ⚫️ 🌐 🗄 🔗, 👈 ⚫️ 🎦 🎓 🧾 ⚙️. diff --git a/docs/em/docs/tutorial/dependencies/sub-dependencies.md b/docs/em/docs/tutorial/dependencies/sub-dependencies.md new file mode 100644 index 0000000000..454ff51298 --- /dev/null +++ b/docs/em/docs/tutorial/dependencies/sub-dependencies.md @@ -0,0 +1,110 @@ +# 🎧-🔗 + +👆 💪 ✍ 🔗 👈 ✔️ **🎧-🔗**. + +👫 💪 **⏬** 👆 💪 👫. + +**FastAPI** 🔜 ✊ 💅 🔬 👫. + +## 🥇 🔗 "☑" + +👆 💪 ✍ 🥇 🔗 ("☑") 💖: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="8-9" + {!> ../../../docs_src/dependencies/tutorial005.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="6-7" + {!> ../../../docs_src/dependencies/tutorial005_py310.py!} + ``` + +⚫️ 📣 📦 🔢 🔢 `q` `str`, & ⤴️ ⚫️ 📨 ⚫️. + +👉 🙅 (🚫 📶 ⚠), ✋️ 🔜 ℹ 👥 🎯 🔛 ❔ 🎧-🔗 👷. + +## 🥈 🔗, "☑" & "⚓️" + +⤴️ 👆 💪 ✍ ➕1️⃣ 🔗 🔢 ("☑") 👈 🎏 🕰 📣 🔗 🚮 👍 (⚫️ "⚓️" 💁‍♂️): + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="13" + {!> ../../../docs_src/dependencies/tutorial005.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="11" + {!> ../../../docs_src/dependencies/tutorial005_py310.py!} + ``` + +➡️ 🎯 🔛 🔢 📣: + +* ✋️ 👉 🔢 🔗 ("☑") ⚫️, ⚫️ 📣 ➕1️⃣ 🔗 (⚫️ "🪀" 🔛 🕳 🙆). + * ⚫️ 🪀 🔛 `query_extractor`, & 🛠️ 💲 📨 ⚫️ 🔢 `q`. +* ⚫️ 📣 📦 `last_query` 🍪, `str`. + * 🚥 👩‍💻 🚫 🚚 🙆 🔢 `q`, 👥 ⚙️ 🏁 🔢 ⚙️, ❔ 👥 🖊 🍪 ⏭. + +## ⚙️ 🔗 + +⤴️ 👥 💪 ⚙️ 🔗 ⏮️: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="22" + {!> ../../../docs_src/dependencies/tutorial005.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial005_py310.py!} + ``` + +!!! info + 👀 👈 👥 🕴 📣 1️⃣ 🔗 *➡ 🛠️ 🔢*, `query_or_cookie_extractor`. + + ✋️ **FastAPI** 🔜 💭 👈 ⚫️ ✔️ ❎ `query_extractor` 🥇, 🚶‍♀️ 🏁 👈 `query_or_cookie_extractor` ⏪ 🤙 ⚫️. + +```mermaid +graph TB + +query_extractor(["query_extractor"]) +query_or_cookie_extractor(["query_or_cookie_extractor"]) + +read_query["/items/"] + +query_extractor --> query_or_cookie_extractor --> read_query +``` + +## ⚙️ 🎏 🔗 💗 🕰 + +🚥 1️⃣ 👆 🔗 📣 💗 🕰 🎏 *➡ 🛠️*, 🖼, 💗 🔗 ✔️ ⚠ 🎧-🔗, **FastAPI** 🔜 💭 🤙 👈 🎧-🔗 🕴 🕐 📍 📨. + +& ⚫️ 🔜 🖊 📨 💲 "💾" & 🚶‍♀️ ⚫️ 🌐 "⚓️" 👈 💪 ⚫️ 👈 🎯 📨, ↩️ 🤙 🔗 💗 🕰 🎏 📨. + +🏧 😐 🌐❔ 👆 💭 👆 💪 🔗 🤙 🔠 🔁 (🎲 💗 🕰) 🎏 📨 ↩️ ⚙️ "💾" 💲, 👆 💪 ⚒ 🔢 `use_cache=False` 🕐❔ ⚙️ `Depends`: + +```Python hl_lines="1" +async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): + return {"fresh_value": fresh_value} +``` + +## 🌃 + +↖️ ⚪️➡️ 🌐 🎀 🔤 ⚙️ 📥, **🔗 💉** ⚙️ 🙅. + +🔢 👈 👀 🎏 *➡ 🛠️ 🔢*. + +✋️, ⚫️ 📶 🏋️, & ✔ 👆 📣 🎲 🙇 🐦 🔗 "📊" (🌲). + +!!! tip + 🌐 👉 💪 🚫 😑 ⚠ ⏮️ 👫 🙅 🖼. + + ✋️ 👆 🔜 👀 ❔ ⚠ ⚫️ 📃 🔃 **💂‍♂**. + + & 👆 🔜 👀 💸 📟 ⚫️ 🔜 🖊 👆. diff --git a/docs/em/docs/tutorial/encoder.md b/docs/em/docs/tutorial/encoder.md new file mode 100644 index 0000000000..75ca3824da --- /dev/null +++ b/docs/em/docs/tutorial/encoder.md @@ -0,0 +1,42 @@ +# 🎻 🔗 🔢 + +📤 💼 🌐❔ 👆 5️⃣📆 💪 🗜 💽 🆎 (💖 Pydantic 🏷) 🕳 🔗 ⏮️ 🎻 (💖 `dict`, `list`, ♒️). + +🖼, 🚥 👆 💪 🏪 ⚫️ 💽. + +👈, **FastAPI** 🚚 `jsonable_encoder()` 🔢. + +## ⚙️ `jsonable_encoder` + +➡️ 🌈 👈 👆 ✔️ 💽 `fake_db` 👈 🕴 📨 🎻 🔗 💽. + +🖼, ⚫️ 🚫 📨 `datetime` 🎚, 👈 🚫 🔗 ⏮️ 🎻. + +, `datetime` 🎚 🔜 ✔️ 🗜 `str` ⚗ 💽 💾 📁. + +🎏 🌌, 👉 💽 🚫🔜 📨 Pydantic 🏷 (🎚 ⏮️ 🔢), 🕴 `dict`. + +👆 💪 ⚙️ `jsonable_encoder` 👈. + +⚫️ 📨 🎚, 💖 Pydantic 🏷, & 📨 🎻 🔗 ⏬: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="5 22" + {!> ../../../docs_src/encoder/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="4 21" + {!> ../../../docs_src/encoder/tutorial001_py310.py!} + ``` + +👉 🖼, ⚫️ 🔜 🗜 Pydantic 🏷 `dict`, & `datetime` `str`. + +🏁 🤙 ⚫️ 🕳 👈 💪 🗜 ⏮️ 🐍 🐩 `json.dumps()`. + +⚫️ 🚫 📨 ⭕ `str` ⚗ 💽 🎻 📁 (🎻). ⚫️ 📨 🐍 🐩 💽 📊 (✅ `dict`) ⏮️ 💲 & 🎧-💲 👈 🌐 🔗 ⏮️ 🎻. + +!!! note + `jsonable_encoder` 🤙 ⚙️ **FastAPI** 🔘 🗜 💽. ✋️ ⚫️ ⚠ 📚 🎏 😐. diff --git a/docs/em/docs/tutorial/extra-data-types.md b/docs/em/docs/tutorial/extra-data-types.md new file mode 100644 index 0000000000..dfdf6141b8 --- /dev/null +++ b/docs/em/docs/tutorial/extra-data-types.md @@ -0,0 +1,82 @@ +# ➕ 💽 🆎 + +🆙 🔜, 👆 ✔️ ⚙️ ⚠ 📊 🆎, 💖: + +* `int` +* `float` +* `str` +* `bool` + +✋️ 👆 💪 ⚙️ 🌅 🏗 📊 🆎. + +& 👆 🔜 ✔️ 🎏 ⚒ 👀 🆙 🔜: + +* 👑 👨‍🎨 🐕‍🦺. +* 💽 🛠️ ⚪️➡️ 📨 📨. +* 💽 🛠️ 📨 💽. +* 💽 🔬. +* 🏧 ✍ & 🧾. + +## 🎏 💽 🆎 + +📥 🌖 📊 🆎 👆 💪 ⚙️: + +* `UUID`: + * 🐩 "⭐ 😍 🆔", ⚠ 🆔 📚 💽 & ⚙️. + * 📨 & 📨 🔜 🎨 `str`. +* `datetime.datetime`: + * 🐍 `datetime.datetime`. + * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `2008-09-15T15:53:00+05:00`. +* `datetime.date`: + * 🐍 `datetime.date`. + * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `2008-09-15`. +* `datetime.time`: + * 🐍 `datetime.time`. + * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `14:23:55.003`. +* `datetime.timedelta`: + * 🐍 `datetime.timedelta`. + * 📨 & 📨 🔜 🎨 `float` 🌐 🥈. + * Pydantic ✔ 🎦 ⚫️ "💾 8️⃣6️⃣0️⃣1️⃣ 🕰 ➕ 🔢", 👀 🩺 🌅 ℹ. +* `frozenset`: + * 📨 & 📨, 😥 🎏 `set`: + * 📨, 📇 🔜 ✍, ❎ ❎ & 🏭 ⚫️ `set`. + * 📨, `set` 🔜 🗜 `list`. + * 🏗 🔗 🔜 ✔ 👈 `set` 💲 😍 (⚙️ 🎻 🔗 `uniqueItems`). +* `bytes`: + * 🐩 🐍 `bytes`. + * 📨 & 📨 🔜 😥 `str`. + * 🏗 🔗 🔜 ✔ 👈 ⚫️ `str` ⏮️ `binary` "📁". +* `Decimal`: + * 🐩 🐍 `Decimal`. + * 📨 & 📨, 🍵 🎏 `float`. +* 👆 💪 ✅ 🌐 ☑ Pydantic 📊 🆎 📥: Pydantic 📊 🆎. + +## 🖼 + +📥 🖼 *➡ 🛠️* ⏮️ 🔢 ⚙️ 🔛 🆎. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 3 12-16" + {!> ../../../docs_src/extra_data_types/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1 2 11-15" + {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} + ``` + +🗒 👈 🔢 🔘 🔢 ✔️ 👫 🐠 💽 🆎, & 👆 💪, 🖼, 🎭 😐 📅 🎭, 💖: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/extra_data_types/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17-18" + {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} + ``` diff --git a/docs/em/docs/tutorial/extra-models.md b/docs/em/docs/tutorial/extra-models.md new file mode 100644 index 0000000000..06c36285d3 --- /dev/null +++ b/docs/em/docs/tutorial/extra-models.md @@ -0,0 +1,252 @@ +# ➕ 🏷 + +▶️ ⏮️ ⏮️ 🖼, ⚫️ 🔜 ⚠ ✔️ 🌅 🌘 1️⃣ 🔗 🏷. + +👉 ✴️ 💼 👩‍💻 🏷, ↩️: + +* **🔢 🏷** 💪 💪 ✔️ 🔐. +* **🔢 🏷** 🔜 🚫 ✔️ 🔐. +* **💽 🏷** 🔜 🎲 💪 ✔️ #️⃣ 🔐. + +!!! danger + 🙅 🏪 👩‍💻 🔢 🔐. 🕧 🏪 "🔐 #️⃣" 👈 👆 💪 ⤴️ ✔. + + 🚥 👆 🚫 💭, 👆 🔜 💡 ⚫️❔ "🔐#️⃣" [💂‍♂ 📃](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. + +## 💗 🏷 + +📥 🏢 💭 ❔ 🏷 💪 👀 💖 ⏮️ 👫 🔐 🏑 & 🥉 🌐❔ 👫 ⚙️: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" + {!> ../../../docs_src/extra_models/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" + {!> ../../../docs_src/extra_models/tutorial001_py310.py!} + ``` + +### 🔃 `**user_in.dict()` + +#### Pydantic `.dict()` + +`user_in` Pydantic 🏷 🎓 `UserIn`. + +Pydantic 🏷 ✔️ `.dict()` 👩‍🔬 👈 📨 `dict` ⏮️ 🏷 💽. + +, 🚥 👥 ✍ Pydantic 🎚 `user_in` 💖: + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +& ⤴️ 👥 🤙: + +```Python +user_dict = user_in.dict() +``` + +👥 🔜 ✔️ `dict` ⏮️ 💽 🔢 `user_dict` (⚫️ `dict` ↩️ Pydantic 🏷 🎚). + +& 🚥 👥 🤙: + +```Python +print(user_dict) +``` + +👥 🔜 🤚 🐍 `dict` ⏮️: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### 🎁 `dict` + +🚥 👥 ✊ `dict` 💖 `user_dict` & 🚶‍♀️ ⚫️ 🔢 (⚖️ 🎓) ⏮️ `**user_dict`, 🐍 🔜 "🎁" ⚫️. ⚫️ 🔜 🚶‍♀️ 🔑 & 💲 `user_dict` 🔗 🔑-💲 ❌. + +, ▶️ ⏮️ `user_dict` ⚪️➡️ 🔛, ✍: + +```Python +UserInDB(**user_dict) +``` + +🔜 🏁 🕳 🌓: + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +⚖️ 🌅 ⚫️❔, ⚙️ `user_dict` 🔗, ⏮️ ⚫️❔ 🎚 ⚫️ 💪 ✔️ 🔮: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### Pydantic 🏷 ⚪️➡️ 🎚 ➕1️⃣ + +🖼 🔛 👥 🤚 `user_dict` ⚪️➡️ `user_in.dict()`, 👉 📟: + +```Python +user_dict = user_in.dict() +UserInDB(**user_dict) +``` + +🔜 🌓: + +```Python +UserInDB(**user_in.dict()) +``` + +...↩️ `user_in.dict()` `dict`, & ⤴️ 👥 ⚒ 🐍 "🎁" ⚫️ 🚶‍♀️ ⚫️ `UserInDB` 🔠 ⏮️ `**`. + +, 👥 🤚 Pydantic 🏷 ⚪️➡️ 💽 ➕1️⃣ Pydantic 🏷. + +#### 🎁 `dict` & ➕ 🇨🇻 + +& ⤴️ ❎ ➕ 🇨🇻 ❌ `hashed_password=hashed_password`, 💖: + +```Python +UserInDB(**user_in.dict(), hashed_password=hashed_password) +``` + +...🔚 🆙 💆‍♂ 💖: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +!!! warning + 🔗 🌖 🔢 🤖 💪 💧 💽, ✋️ 👫 ↗️ 🚫 🚚 🙆 🎰 💂‍♂. + +## 📉 ❎ + +📉 📟 ❎ 1️⃣ 🐚 💭 **FastAPI**. + +📟 ❎ 📈 🤞 🐛, 💂‍♂ ❔, 📟 🔁 ❔ (🕐❔ 👆 ℹ 1️⃣ 🥉 ✋️ 🚫 🎏), ♒️. + +& 👉 🏷 🌐 🤝 📚 💽 & ❎ 🔢 📛 & 🆎. + +👥 💪 👻. + +👥 💪 📣 `UserBase` 🏷 👈 🍦 🧢 👆 🎏 🏷. & ⤴️ 👥 💪 ⚒ 🏿 👈 🏷 👈 😖 🚮 🔢 (🆎 📄, 🔬, ♒️). + +🌐 💽 🛠️, 🔬, 🧾, ♒️. 🔜 👷 🛎. + +👈 🌌, 👥 💪 📣 🔺 🖖 🏷 (⏮️ 🔢 `password`, ⏮️ `hashed_password` & 🍵 🔐): + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9 15-16 19-20 23-24" + {!> ../../../docs_src/extra_models/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7 13-14 17-18 21-22" + {!> ../../../docs_src/extra_models/tutorial002_py310.py!} + ``` + +## `Union` ⚖️ `anyOf` + +👆 💪 📣 📨 `Union` 2️⃣ 🆎, 👈 ⛓, 👈 📨 🔜 🙆 2️⃣. + +⚫️ 🔜 🔬 🗄 ⏮️ `anyOf`. + +👈, ⚙️ 🐩 🐍 🆎 🔑 `typing.Union`: + +!!! note + 🕐❔ ⚖ `Union`, 🔌 🏆 🎯 🆎 🥇, ⏩ 🌘 🎯 🆎. 🖼 🔛, 🌖 🎯 `PlaneItem` 👟 ⏭ `CarItem` `Union[PlaneItem, CarItem]`. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003_py310.py!} + ``` + +### `Union` 🐍 3️⃣.1️⃣0️⃣ + +👉 🖼 👥 🚶‍♀️ `Union[PlaneItem, CarItem]` 💲 ❌ `response_model`. + +↩️ 👥 🚶‍♀️ ⚫️ **💲 ❌** ↩️ 🚮 ⚫️ **🆎 ✍**, 👥 ✔️ ⚙️ `Union` 🐍 3️⃣.1️⃣0️⃣. + +🚥 ⚫️ 🆎 ✍ 👥 💪 ✔️ ⚙️ ⏸ ⏸,: + +```Python +some_variable: PlaneItem | CarItem +``` + +✋️ 🚥 👥 🚮 👈 `response_model=PlaneItem | CarItem` 👥 🔜 🤚 ❌, ↩️ 🐍 🔜 🔄 🎭 **❌ 🛠️** 🖖 `PlaneItem` & `CarItem` ↩️ 🔬 👈 🆎 ✍. + +## 📇 🏷 + +🎏 🌌, 👆 💪 📣 📨 📇 🎚. + +👈, ⚙️ 🐩 🐍 `typing.List` (⚖️ `list` 🐍 3️⃣.9️⃣ & 🔛): + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 20" + {!> ../../../docs_src/extra_models/tutorial004.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/extra_models/tutorial004_py39.py!} + ``` + +## 📨 ⏮️ ❌ `dict` + +👆 💪 📣 📨 ⚙️ ✅ ❌ `dict`, 📣 🆎 🔑 & 💲, 🍵 ⚙️ Pydantic 🏷. + +👉 ⚠ 🚥 👆 🚫 💭 ☑ 🏑/🔢 📛 (👈 🔜 💪 Pydantic 🏷) ⏪. + +👉 💼, 👆 💪 ⚙️ `typing.Dict` (⚖️ `dict` 🐍 3️⃣.9️⃣ & 🔛): + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="1 8" + {!> ../../../docs_src/extra_models/tutorial005.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="6" + {!> ../../../docs_src/extra_models/tutorial005_py39.py!} + ``` + +## 🌃 + +⚙️ 💗 Pydantic 🏷 & 😖 ➡ 🔠 💼. + +👆 🚫 💪 ✔️ 👁 💽 🏷 📍 👨‍💼 🚥 👈 👨‍💼 🔜 💪 ✔️ 🎏 "🇵🇸". 💼 ⏮️ 👩‍💻 "👨‍💼" ⏮️ 🇵🇸 ✅ `password`, `password_hash` & 🙅‍♂ 🔐. diff --git a/docs/em/docs/tutorial/first-steps.md b/docs/em/docs/tutorial/first-steps.md new file mode 100644 index 0000000000..252e769f41 --- /dev/null +++ b/docs/em/docs/tutorial/first-steps.md @@ -0,0 +1,333 @@ +# 🥇 🔁 + +🙅 FastAPI 📁 💪 👀 💖 👉: + +```Python +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +📁 👈 📁 `main.py`. + +🏃 🖖 💽: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +!!! note + 📋 `uvicorn main:app` 🔗: + + * `main`: 📁 `main.py` (🐍 "🕹"). + * `app`: 🎚 ✍ 🔘 `main.py` ⏮️ ⏸ `app = FastAPI()`. + * `--reload`: ⚒ 💽 ⏏ ⏮️ 📟 🔀. 🕴 ⚙️ 🛠️. + +🔢, 📤 ⏸ ⏮️ 🕳 💖: + +```hl_lines="4" +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +👈 ⏸ 🎦 📛 🌐❔ 👆 📱 ➖ 🍦, 👆 🇧🇿 🎰. + +### ✅ ⚫️ + +📂 👆 🖥 http://127.0.0.1:8000. + +👆 🔜 👀 🎻 📨: + +```JSON +{"message": "Hello World"} +``` + +### 🎓 🛠️ 🩺 + +🔜 🚶 http://127.0.0.1:8000/docs. + +👆 🔜 👀 🏧 🎓 🛠️ 🧾 (🚚 🦁 🎚): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### 🎛 🛠️ 🩺 + +& 🔜, 🚶 http://127.0.0.1:8000/redoc. + +👆 🔜 👀 🎛 🏧 🧾 (🚚 📄): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +### 🗄 + +**FastAPI** 🏗 "🔗" ⏮️ 🌐 👆 🛠️ ⚙️ **🗄** 🐩 ⚖ 🔗. + +#### "🔗" + +"🔗" 🔑 ⚖️ 📛 🕳. 🚫 📟 👈 🛠️ ⚫️, ✋️ 📝 📛. + +#### 🛠️ "🔗" + +👉 💼, 🗄 🔧 👈 🤔 ❔ 🔬 🔗 👆 🛠️. + +👉 🔗 🔑 🔌 👆 🛠️ ➡, 💪 🔢 👫 ✊, ♒️. + +#### 💽 "🔗" + +⚖ "🔗" 💪 🔗 💠 💽, 💖 🎻 🎚. + +👈 💼, ⚫️ 🔜 ⛓ 🎻 🔢, & 📊 🆎 👫 ✔️, ♒️. + +#### 🗄 & 🎻 🔗 + +🗄 🔬 🛠️ 🔗 👆 🛠️. & 👈 🔗 🔌 🔑 (⚖️ "🔗") 📊 📨 & 📨 👆 🛠️ ⚙️ **🎻 🔗**, 🐩 🎻 📊 🔗. + +#### ✅ `openapi.json` + +🚥 👆 😟 🔃 ❔ 🍣 🗄 🔗 👀 💖, FastAPI 🔁 🏗 🎻 (🔗) ⏮️ 📛 🌐 👆 🛠️. + +👆 💪 👀 ⚫️ 🔗: http://127.0.0.1:8000/openapi.json. + +⚫️ 🔜 🎦 🎻 ▶️ ⏮️ 🕳 💖: + +```JSON +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +#### ⚫️❔ 🗄 + +🗄 🔗 ⚫️❔ 🏋️ 2️⃣ 🎓 🧾 ⚙️ 🔌. + +& 📤 💯 🎛, 🌐 ⚓️ 🔛 🗄. 👆 💪 💪 🚮 🙆 📚 🎛 👆 🈸 🏗 ⏮️ **FastAPI**. + +👆 💪 ⚙️ ⚫️ 🏗 📟 🔁, 👩‍💻 👈 🔗 ⏮️ 👆 🛠️. 🖼, 🕸, 📱 ⚖️ ☁ 🈸. + +## 🌃, 🔁 🔁 + +### 🔁 1️⃣: 🗄 `FastAPI` + +```Python hl_lines="1" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`FastAPI` 🐍 🎓 👈 🚚 🌐 🛠️ 👆 🛠️. + +!!! note "📡 ℹ" + `FastAPI` 🎓 👈 😖 🔗 ⚪️➡️ `Starlette`. + + 👆 💪 ⚙️ 🌐 💃 🛠️ ⏮️ `FastAPI` 💁‍♂️. + +### 🔁 2️⃣: ✍ `FastAPI` "👐" + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +📥 `app` 🔢 🔜 "👐" 🎓 `FastAPI`. + +👉 🔜 👑 ☝ 🔗 ✍ 🌐 👆 🛠️. + +👉 `app` 🎏 1️⃣ 🔗 `uvicorn` 📋: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +🚥 👆 ✍ 👆 📱 💖: + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial002.py!} +``` + +& 🚮 ⚫️ 📁 `main.py`, ⤴️ 👆 🔜 🤙 `uvicorn` 💖: + +
+ +```console +$ uvicorn main:my_awesome_api --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### 🔁 3️⃣: ✍ *➡ 🛠️* + +#### ➡ + +"➡" 📥 🔗 🏁 🍕 📛 ▶️ ⚪️➡️ 🥇 `/`. + +, 📛 💖: + +``` +https://example.com/items/foo +``` + +...➡ 🔜: + +``` +/items/foo +``` + +!!! info + "➡" 🛎 🤙 "🔗" ⚖️ "🛣". + +⏪ 🏗 🛠️, "➡" 👑 🌌 🎏 "⚠" & "ℹ". + +#### 🛠️ + +"🛠️" 📥 🔗 1️⃣ 🇺🇸🔍 "👩‍🔬". + +1️⃣: + +* `POST` +* `GET` +* `PUT` +* `DELETE` + +...& 🌅 😍 🕐: + +* `OPTIONS` +* `HEAD` +* `PATCH` +* `TRACE` + +🇺🇸🔍 🛠️, 👆 💪 🔗 🔠 ➡ ⚙️ 1️⃣ (⚖️ 🌅) 👫 "👩‍🔬". + +--- + +🕐❔ 🏗 🔗, 👆 🛎 ⚙️ 👫 🎯 🇺🇸🔍 👩‍🔬 🎭 🎯 🎯. + +🛎 👆 ⚙️: + +* `POST`: ✍ 💽. +* `GET`: ✍ 💽. +* `PUT`: ℹ 💽. +* `DELETE`: ❎ 💽. + +, 🗄, 🔠 🇺🇸🔍 👩‍🔬 🤙 "🛠️". + +👥 🔜 🤙 👫 "**🛠️**" 💁‍♂️. + +#### 🔬 *➡ 🛠️ 👨‍🎨* + +```Python hl_lines="6" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`@app.get("/")` 💬 **FastAPI** 👈 🔢 ▶️️ 🔛 🈚 🚚 📨 👈 🚶: + +* ➡ `/` +* ⚙️ get 🛠️ + +!!! info "`@decorator` ℹ" + 👈 `@something` ❕ 🐍 🤙 "👨‍🎨". + + 👆 🚮 ⚫️ 🔛 🔝 🔢. 💖 📶 📔 👒 (👤 💭 👈 🌐❔ ⚖ 👟 ⚪️➡️). + + "👨‍🎨" ✊ 🔢 🔛 & 🔨 🕳 ⏮️ ⚫️. + + 👆 💼, 👉 👨‍🎨 💬 **FastAPI** 👈 🔢 🔛 🔗 **➡** `/` ⏮️ **🛠️** `get`. + + ⚫️ "**➡ 🛠️ 👨‍🎨**". + +👆 💪 ⚙️ 🎏 🛠️: + +* `@app.post()` +* `@app.put()` +* `@app.delete()` + +& 🌅 😍 🕐: + +* `@app.options()` +* `@app.head()` +* `@app.patch()` +* `@app.trace()` + +!!! tip + 👆 🆓 ⚙️ 🔠 🛠️ (🇺🇸🔍 👩‍🔬) 👆 🎋. + + **FastAPI** 🚫 🛠️ 🙆 🎯 🔑. + + ℹ 📥 🎁 📄, 🚫 📄. + + 🖼, 🕐❔ ⚙️ 🕹 👆 🛎 🎭 🌐 🎯 ⚙️ 🕴 `POST` 🛠️. + +### 🔁 4️⃣: 🔬 **➡ 🛠️ 🔢** + +👉 👆 "**➡ 🛠️ 🔢**": + +* **➡**: `/`. +* **🛠️**: `get`. +* **🔢**: 🔢 🔛 "👨‍🎨" (🔛 `@app.get("/")`). + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +👉 🐍 🔢. + +⚫️ 🔜 🤙 **FastAPI** 🕐❔ ⚫️ 📨 📨 📛 "`/`" ⚙️ `GET` 🛠️. + +👉 💼, ⚫️ `async` 🔢. + +--- + +👆 💪 🔬 ⚫️ 😐 🔢 ↩️ `async def`: + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial003.py!} +``` + +!!! note + 🚥 👆 🚫 💭 🔺, ✅ [🔁: *"🏃 ❓"*](../async.md#in-a-hurry){.internal-link target=_blank}. + +### 🔁 5️⃣: 📨 🎚 + +```Python hl_lines="8" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +👆 💪 📨 `dict`, `list`, ⭐ 💲 `str`, `int`, ♒️. + +👆 💪 📨 Pydantic 🏷 (👆 🔜 👀 🌅 🔃 👈 ⏪). + +📤 📚 🎏 🎚 & 🏷 👈 🔜 🔁 🗜 🎻 (🔌 🐜, ♒️). 🔄 ⚙️ 👆 💕 🕐, ⚫️ 🏆 🎲 👈 👫 ⏪ 🐕‍🦺. + +## 🌃 + +* 🗄 `FastAPI`. +* ✍ `app` 👐. +* ✍ **➡ 🛠️ 👨‍🎨** (💖 `@app.get("/")`). +* ✍ **➡ 🛠️ 🔢** (💖 `def root(): ...` 🔛). +* 🏃 🛠️ 💽 (💖 `uvicorn main:app --reload`). diff --git a/docs/em/docs/tutorial/handling-errors.md b/docs/em/docs/tutorial/handling-errors.md new file mode 100644 index 0000000000..ef7bbfa651 --- /dev/null +++ b/docs/em/docs/tutorial/handling-errors.md @@ -0,0 +1,261 @@ +# 🚚 ❌ + +📤 📚 ⚠ 🌐❔ 👆 💪 🚨 ❌ 👩‍💻 👈 ⚙️ 👆 🛠️. + +👉 👩‍💻 💪 🖥 ⏮️ 🕸, 📟 ⚪️➡️ 👱 🙆, ☁ 📳, ♒️. + +👆 💪 💪 💬 👩‍💻 👈: + +* 👩‍💻 🚫 ✔️ 🥃 😌 👈 🛠️. +* 👩‍💻 🚫 ✔️ 🔐 👈 ℹ. +* 🏬 👩‍💻 🔄 🔐 🚫 🔀. +* ♒️. + +👫 💼, 👆 🔜 🛎 📨 **🇺🇸🔍 👔 📟** ↔ **4️⃣0️⃣0️⃣** (⚪️➡️ 4️⃣0️⃣0️⃣ 4️⃣9️⃣9️⃣). + +👉 🎏 2️⃣0️⃣0️⃣ 🇺🇸🔍 👔 📟 (⚪️➡️ 2️⃣0️⃣0️⃣ 2️⃣9️⃣9️⃣). 👈 "2️⃣0️⃣0️⃣" 👔 📟 ⛓ 👈 😫 📤 "🏆" 📨. + +👔 📟 4️⃣0️⃣0️⃣ ↔ ⛓ 👈 📤 ❌ ⚪️➡️ 👩‍💻. + +💭 🌐 👈 **"4️⃣0️⃣4️⃣ 🚫 🔎"** ❌ (& 🤣) ❓ + +## ⚙️ `HTTPException` + +📨 🇺🇸🔍 📨 ⏮️ ❌ 👩‍💻 👆 ⚙️ `HTTPException`. + +### 🗄 `HTTPException` + +```Python hl_lines="1" +{!../../../docs_src/handling_errors/tutorial001.py!} +``` + +### 🤚 `HTTPException` 👆 📟 + +`HTTPException` 😐 🐍 ⚠ ⏮️ 🌖 📊 🔗 🔗. + +↩️ ⚫️ 🐍 ⚠, 👆 🚫 `return` ⚫️, 👆 `raise` ⚫️. + +👉 ⛓ 👈 🚥 👆 🔘 🚙 🔢 👈 👆 🤙 🔘 👆 *➡ 🛠️ 🔢*, & 👆 🤚 `HTTPException` ⚪️➡️ 🔘 👈 🚙 🔢, ⚫️ 🏆 🚫 🏃 🎂 📟 *➡ 🛠️ 🔢*, ⚫️ 🔜 ❎ 👈 📨 ▶️️ ↖️ & 📨 🇺🇸🔍 ❌ ⚪️➡️ `HTTPException` 👩‍💻. + +💰 🙋‍♀ ⚠ 🤭 `return`😅 💲 🔜 🌖 ⭐ 📄 🔃 🔗 & 💂‍♂. + +👉 🖼, 🕐❔ 👩‍💻 📨 🏬 🆔 👈 🚫 🔀, 🤚 ⚠ ⏮️ 👔 📟 `404`: + +```Python hl_lines="11" +{!../../../docs_src/handling_errors/tutorial001.py!} +``` + +### 📉 📨 + +🚥 👩‍💻 📨 `http://example.com/items/foo` ( `item_id` `"foo"`), 👈 👩‍💻 🔜 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣0️⃣, & 🎻 📨: + +```JSON +{ + "item": "The Foo Wrestlers" +} +``` + +✋️ 🚥 👩‍💻 📨 `http://example.com/items/bar` (🚫-🚫 `item_id` `"bar"`), 👈 👩‍💻 🔜 📨 🇺🇸🔍 👔 📟 4️⃣0️⃣4️⃣ ("🚫 🔎" ❌), & 🎻 📨: + +```JSON +{ + "detail": "Item not found" +} +``` + +!!! tip + 🕐❔ 🙋‍♀ `HTTPException`, 👆 💪 🚶‍♀️ 🙆 💲 👈 💪 🗜 🎻 🔢 `detail`, 🚫 🕴 `str`. + + 👆 💪 🚶‍♀️ `dict`, `list`, ♒️. + + 👫 🍵 🔁 **FastAPI** & 🗜 🎻. + +## 🚮 🛃 🎚 + +📤 ⚠ 🌐❔ ⚫️ ⚠ 💪 🚮 🛃 🎚 🇺🇸🔍 ❌. 🖼, 🆎 💂‍♂. + +👆 🎲 🏆 🚫 💪 ⚙️ ⚫️ 🔗 👆 📟. + +✋️ 💼 👆 💪 ⚫️ 🏧 😐, 👆 💪 🚮 🛃 🎚: + +```Python hl_lines="14" +{!../../../docs_src/handling_errors/tutorial002.py!} +``` + +## ❎ 🛃 ⚠ 🐕‍🦺 + +👆 💪 🚮 🛃 ⚠ 🐕‍🦺 ⏮️ 🎏 ⚠ 🚙 ⚪️➡️ 💃. + +➡️ 💬 👆 ✔️ 🛃 ⚠ `UnicornException` 👈 👆 (⚖️ 🗃 👆 ⚙️) 💪 `raise`. + +& 👆 💚 🍵 👉 ⚠ 🌐 ⏮️ FastAPI. + +👆 💪 🚮 🛃 ⚠ 🐕‍🦺 ⏮️ `@app.exception_handler()`: + +```Python hl_lines="5-7 13-18 24" +{!../../../docs_src/handling_errors/tutorial003.py!} +``` + +📥, 🚥 👆 📨 `/unicorns/yolo`, *➡ 🛠️* 🔜 `raise` `UnicornException`. + +✋️ ⚫️ 🔜 🍵 `unicorn_exception_handler`. + +, 👆 🔜 📨 🧹 ❌, ⏮️ 🇺🇸🔍 👔 📟 `418` & 🎻 🎚: + +```JSON +{"message": "Oops! yolo did something. There goes a rainbow..."} +``` + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.requests import Request` & `from starlette.responses import JSONResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `Request`. + +## 🔐 🔢 ⚠ 🐕‍🦺 + +**FastAPI** ✔️ 🔢 ⚠ 🐕‍🦺. + +👫 🐕‍🦺 🈚 🛬 🔢 🎻 📨 🕐❔ 👆 `raise` `HTTPException` & 🕐❔ 📨 ✔️ ❌ 💽. + +👆 💪 🔐 👫 ⚠ 🐕‍🦺 ⏮️ 👆 👍. + +### 🔐 📨 🔬 ⚠ + +🕐❔ 📨 🔌 ❌ 📊, **FastAPI** 🔘 🤚 `RequestValidationError`. + +& ⚫️ 🔌 🔢 ⚠ 🐕‍🦺 ⚫️. + +🔐 ⚫️, 🗄 `RequestValidationError` & ⚙️ ⚫️ ⏮️ `@app.exception_handler(RequestValidationError)` 🎀 ⚠ 🐕‍🦺. + +⚠ 🐕‍🦺 🔜 📨 `Request` & ⚠. + +```Python hl_lines="2 14-16" +{!../../../docs_src/handling_errors/tutorial004.py!} +``` + +🔜, 🚥 👆 🚶 `/items/foo`, ↩️ 💆‍♂ 🔢 🎻 ❌ ⏮️: + +```JSON +{ + "detail": [ + { + "loc": [ + "path", + "item_id" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +👆 🔜 🤚 ✍ ⏬, ⏮️: + +``` +1 validation error +path -> item_id + value is not a valid integer (type=type_error.integer) +``` + +#### `RequestValidationError` 🆚 `ValidationError` + +!!! warning + 👫 📡 ℹ 👈 👆 💪 🚶 🚥 ⚫️ 🚫 ⚠ 👆 🔜. + +`RequestValidationError` 🎧-🎓 Pydantic `ValidationError`. + +**FastAPI** ⚙️ ⚫️ 👈, 🚥 👆 ⚙️ Pydantic 🏷 `response_model`, & 👆 💽 ✔️ ❌, 👆 🔜 👀 ❌ 👆 🕹. + +✋️ 👩‍💻/👩‍💻 🔜 🚫 👀 ⚫️. ↩️, 👩‍💻 🔜 📨 "🔗 💽 ❌" ⏮️ 🇺🇸🔍 👔 📟 `500`. + +⚫️ 🔜 👉 🌌 ↩️ 🚥 👆 ✔️ Pydantic `ValidationError` 👆 *📨* ⚖️ 🙆 👆 📟 (🚫 👩‍💻 *📨*), ⚫️ 🤙 🐛 👆 📟. + +& ⏪ 👆 🔧 ⚫️, 👆 👩‍💻/👩‍💻 🚫🔜 🚫 ✔️ 🔐 🔗 ℹ 🔃 ❌, 👈 💪 🎦 💂‍♂ ⚠. + +### 🔐 `HTTPException` ❌ 🐕‍🦺 + +🎏 🌌, 👆 💪 🔐 `HTTPException` 🐕‍🦺. + +🖼, 👆 💪 💚 📨 ✅ ✍ 📨 ↩️ 🎻 👫 ❌: + +```Python hl_lines="3-4 9-11 22" +{!../../../docs_src/handling_errors/tutorial004.py!} +``` + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.responses import PlainTextResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. + +### ⚙️ `RequestValidationError` 💪 + +`RequestValidationError` 🔌 `body` ⚫️ 📨 ⏮️ ❌ 💽. + +👆 💪 ⚙️ ⚫️ ⏪ 🛠️ 👆 📱 🕹 💪 & ℹ ⚫️, 📨 ⚫️ 👩‍💻, ♒️. + +```Python hl_lines="14" +{!../../../docs_src/handling_errors/tutorial005.py!} +``` + +🔜 🔄 📨 ❌ 🏬 💖: + +```JSON +{ + "title": "towel", + "size": "XL" +} +``` + +👆 🔜 📨 📨 💬 👆 👈 💽 ❌ ⚗ 📨 💪: + +```JSON hl_lines="12-15" +{ + "detail": [ + { + "loc": [ + "body", + "size" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ], + "body": { + "title": "towel", + "size": "XL" + } +} +``` + +#### FastAPI `HTTPException` 🆚 💃 `HTTPException` + +**FastAPI** ✔️ 🚮 👍 `HTTPException`. + +& **FastAPI**'Ⓜ `HTTPException` ❌ 🎓 😖 ⚪️➡️ 💃 `HTTPException` ❌ 🎓. + +🕴 🔺, 👈 **FastAPI**'Ⓜ `HTTPException` ✔ 👆 🚮 🎚 🔌 📨. + +👉 💪/⚙️ 🔘 ✳ 2️⃣.0️⃣ & 💂‍♂ 🚙. + +, 👆 💪 🚧 🙋‍♀ **FastAPI**'Ⓜ `HTTPException` 🛎 👆 📟. + +✋️ 🕐❔ 👆 ® ⚠ 🐕‍🦺, 👆 🔜 ® ⚫️ 💃 `HTTPException`. + +👉 🌌, 🚥 🙆 🍕 💃 🔗 📟, ⚖️ 💃 ↔ ⚖️ 🔌 -, 🤚 💃 `HTTPException`, 👆 🐕‍🦺 🔜 💪 ✊ & 🍵 ⚫️. + +👉 🖼, 💪 ✔️ 👯‍♂️ `HTTPException`Ⓜ 🎏 📟, 💃 ⚠ 📁 `StarletteHTTPException`: + +```Python +from starlette.exceptions import HTTPException as StarletteHTTPException +``` + +### 🏤-⚙️ **FastAPI**'Ⓜ ⚠ 🐕‍🦺 + +🚥 👆 💚 ⚙️ ⚠ ⤴️ ⏮️ 🎏 🔢 ⚠ 🐕‍🦺 ⚪️➡️ **FastAPI**, 👆 💪 🗄 & 🏤-⚙️ 🔢 ⚠ 🐕‍🦺 ⚪️➡️ `fastapi.exception_handlers`: + +```Python hl_lines="2-5 15 21" +{!../../../docs_src/handling_errors/tutorial006.py!} +``` + +👉 🖼 👆 `print`😅 ❌ ⏮️ 📶 🎨 📧, ✋️ 👆 🤚 💭. 👆 💪 ⚙️ ⚠ & ⤴️ 🏤-⚙️ 🔢 ⚠ 🐕‍🦺. diff --git a/docs/em/docs/tutorial/header-params.md b/docs/em/docs/tutorial/header-params.md new file mode 100644 index 0000000000..0f33a17747 --- /dev/null +++ b/docs/em/docs/tutorial/header-params.md @@ -0,0 +1,128 @@ +# 🎚 🔢 + +👆 💪 🔬 🎚 🔢 🎏 🌌 👆 🔬 `Query`, `Path` & `Cookie` 🔢. + +## 🗄 `Header` + +🥇 🗄 `Header`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3" + {!> ../../../docs_src/header_params/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/header_params/tutorial001_py310.py!} + ``` + +## 📣 `Header` 🔢 + +⤴️ 📣 🎚 🔢 ⚙️ 🎏 📊 ⏮️ `Path`, `Query` & `Cookie`. + +🥇 💲 🔢 💲, 👆 💪 🚶‍♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/header_params/tutorial001_py310.py!} + ``` + +!!! note "📡 ℹ" + `Header` "👭" 🎓 `Path`, `Query` & `Cookie`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓. + + ✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `Header`, & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. + +!!! info + 📣 🎚, 👆 💪 ⚙️ `Header`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢. + +## 🏧 🛠️ + +`Header` ✔️ 🐥 ➕ 🛠️ 🔛 🔝 ⚫️❔ `Path`, `Query` & `Cookie` 🚚. + +🌅 🐩 🎚 🎏 "🔠" 🦹, 💭 "➖ 🔣" (`-`). + +✋️ 🔢 💖 `user-agent` ❌ 🐍. + +, 🔢, `Header` 🔜 🗜 🔢 📛 🦹 ⚪️➡️ 🎦 (`_`) 🔠 (`-`) ⚗ & 📄 🎚. + +, 🇺🇸🔍 🎚 💼-😛,, 👆 💪 📣 👫 ⏮️ 🐩 🐍 👗 (💭 "🔡"). + +, 👆 💪 ⚙️ `user_agent` 👆 🛎 🔜 🐍 📟, ↩️ 💆‍♂ 🎯 🥇 🔤 `User_Agent` ⚖️ 🕳 🎏. + +🚥 🤔 👆 💪 ❎ 🏧 🛠️ 🎦 🔠, ⚒ 🔢 `convert_underscores` `Header` `False`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/header_params/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="8" + {!> ../../../docs_src/header_params/tutorial002_py310.py!} + ``` + +!!! warning + ⏭ ⚒ `convert_underscores` `False`, 🐻 🤯 👈 🇺🇸🔍 🗳 & 💽 / ⚙️ 🎚 ⏮️ 🎦. + +## ❎ 🎚 + +⚫️ 💪 📨 ❎ 🎚. 👈 ⛓, 🎏 🎚 ⏮️ 💗 💲. + +👆 💪 🔬 👈 💼 ⚙️ 📇 🆎 📄. + +👆 🔜 📨 🌐 💲 ⚪️➡️ ❎ 🎚 🐍 `list`. + +🖼, 📣 🎚 `X-Token` 👈 💪 😑 🌅 🌘 🕐, 👆 💪 ✍: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial003.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial003_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/header_params/tutorial003_py310.py!} + ``` + +🚥 👆 🔗 ⏮️ 👈 *➡ 🛠️* 📨 2️⃣ 🇺🇸🔍 🎚 💖: + +``` +X-Token: foo +X-Token: bar +``` + +📨 🔜 💖: + +```JSON +{ + "X-Token values": [ + "bar", + "foo" + ] +} +``` + +## 🌃 + +📣 🎚 ⏮️ `Header`, ⚙️ 🎏 ⚠ ⚓ `Query`, `Path` & `Cookie`. + +& 🚫 😟 🔃 🎦 👆 🔢, **FastAPI** 🔜 ✊ 💅 🏭 👫. diff --git a/docs/em/docs/tutorial/index.md b/docs/em/docs/tutorial/index.md new file mode 100644 index 0000000000..26b4c1913a --- /dev/null +++ b/docs/em/docs/tutorial/index.md @@ -0,0 +1,80 @@ +# 🔰 - 👩‍💻 🦮 + +👉 🔰 🎦 👆 ❔ ⚙️ **FastAPI** ⏮️ 🌅 🚮 ⚒, 🔁 🔁. + +🔠 📄 📉 🏗 🔛 ⏮️ 🕐, ✋️ ⚫️ 🏗 🎏 ❔, 👈 👆 💪 🚶 🔗 🙆 🎯 1️⃣ ❎ 👆 🎯 🛠️ 💪. + +⚫️ 🏗 👷 🔮 🔗. + +👆 💪 👟 🔙 & 👀 ⚫️❔ ⚫️❔ 👆 💪. + +## 🏃 📟 + +🌐 📟 🍫 💪 📁 & ⚙️ 🔗 (👫 🤙 💯 🐍 📁). + +🏃 🙆 🖼, 📁 📟 📁 `main.py`, & ▶️ `uvicorn` ⏮️: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +⚫️ **🏆 💡** 👈 👆 ✍ ⚖️ 📁 📟, ✍ ⚫️ & 🏃 ⚫️ 🌐. + +⚙️ ⚫️ 👆 👨‍🎨 ⚫️❔ 🤙 🎦 👆 💰 FastAPI, 👀 ❔ 🐥 📟 👆 ✔️ ✍, 🌐 🆎 ✅, ✍, ♒️. + +--- + +## ❎ FastAPI + +🥇 🔁 ❎ FastAPI. + +🔰, 👆 💪 💚 ❎ ⚫️ ⏮️ 🌐 📦 🔗 & ⚒: + +
+ +```console +$ pip install "fastapi[all]" + +---> 100% +``` + +
+ +...👈 🔌 `uvicorn`, 👈 👆 💪 ⚙️ 💽 👈 🏃 👆 📟. + +!!! note + 👆 💪 ❎ ⚫️ 🍕 🍕. + + 👉 ⚫️❔ 👆 🔜 🎲 🕐 👆 💚 🛠️ 👆 🈸 🏭: + + ``` + pip install fastapi + ``` + + ❎ `uvicorn` 👷 💽: + + ``` + pip install "uvicorn[standard]" + ``` + + & 🎏 🔠 📦 🔗 👈 👆 💚 ⚙️. + +## 🏧 👩‍💻 🦮 + +📤 **🏧 👩‍💻 🦮** 👈 👆 💪 ✍ ⏪ ⏮️ 👉 **🔰 - 👩‍💻 🦮**. + +**🏧 👩‍💻 🦮**, 🏗 🔛 👉, ⚙️ 🎏 🔧, & 💡 👆 ➕ ⚒. + +✋️ 👆 🔜 🥇 ✍ **🔰 - 👩‍💻 🦮** (⚫️❔ 👆 👂 ▶️️ 🔜). + +⚫️ 🔧 👈 👆 💪 🏗 🏁 🈸 ⏮️ **🔰 - 👩‍💻 🦮**, & ⤴️ ↔ ⚫️ 🎏 🌌, ⚓️ 🔛 👆 💪, ⚙️ 🌖 💭 ⚪️➡️ **🏧 👩‍💻 🦮**. diff --git a/docs/em/docs/tutorial/metadata.md b/docs/em/docs/tutorial/metadata.md new file mode 100644 index 0000000000..00098cdf59 --- /dev/null +++ b/docs/em/docs/tutorial/metadata.md @@ -0,0 +1,112 @@ +# 🗃 & 🩺 📛 + +👆 💪 🛃 📚 🗃 📳 👆 **FastAPI** 🈸. + +## 🗃 🛠️ + +👆 💪 ⚒ 📄 🏑 👈 ⚙️ 🗄 🔧 & 🏧 🛠️ 🩺 ⚜: + +| 🔢 | 🆎 | 📛 | +|------------|------|-------------| +| `title` | `str` | 📛 🛠️. | +| `description` | `str` | 📏 📛 🛠️. ⚫️ 💪 ⚙️ ✍. | +| `version` | `string` | ⏬ 🛠️. 👉 ⏬ 👆 👍 🈸, 🚫 🗄. 🖼 `2.5.0`. | +| `terms_of_service` | `str` | 📛 ⚖ 🐕‍🦺 🛠️. 🚥 🚚, 👉 ✔️ 📛. | +| `contact` | `dict` | 📧 ℹ 🎦 🛠️. ⚫️ 💪 🔌 📚 🏑.
contact 🏑
🔢🆎📛
namestr⚖ 📛 📧 👨‍💼/🏢.
urlstr📛 ☝ 📧 ℹ. 🔜 📁 📛.
emailstr📧 📢 📧 👨‍💼/🏢. 🔜 📁 📧 📢.
| +| `license_info` | `dict` | 🛂 ℹ 🎦 🛠️. ⚫️ 💪 🔌 📚 🏑.
license_info 🏑
🔢🆎📛
namestr🚚 (🚥 license_info ⚒). 🛂 📛 ⚙️ 🛠️.
urlstr📛 🛂 ⚙️ 🛠️. 🔜 📁 📛.
| + +👆 💪 ⚒ 👫 ⏩: + +```Python hl_lines="3-16 19-31" +{!../../../docs_src/metadata/tutorial001.py!} +``` + +!!! tip + 👆 💪 ✍ ✍ `description` 🏑 & ⚫️ 🔜 ✍ 🔢. + +⏮️ 👉 📳, 🏧 🛠️ 🩺 🔜 👀 💖: + + + +## 🗃 🔖 + +👆 💪 🚮 🌖 🗃 🎏 🔖 ⚙️ 👪 👆 ➡ 🛠️ ⏮️ 🔢 `openapi_tags`. + +⚫️ ✊ 📇 ⚗ 1️⃣ 📖 🔠 🔖. + +🔠 📖 💪 🔌: + +* `name` (**✔**): `str` ⏮️ 🎏 📛 👆 ⚙️ `tags` 🔢 👆 *➡ 🛠️* & `APIRouter`Ⓜ. +* `description`: `str` ⏮️ 📏 📛 🔖. ⚫️ 💪 ✔️ ✍ & 🔜 🎦 🩺 🎚. +* `externalDocs`: `dict` 🔬 🔢 🧾 ⏮️: + * `description`: `str` ⏮️ 📏 📛 🔢 🩺. + * `url` (**✔**): `str` ⏮️ 📛 🔢 🧾. + +### ✍ 🗃 🔖 + +➡️ 🔄 👈 🖼 ⏮️ 🔖 `users` & `items`. + +✍ 🗃 👆 🔖 & 🚶‍♀️ ⚫️ `openapi_tags` 🔢: + +```Python hl_lines="3-16 18" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +👀 👈 👆 💪 ⚙️ ✍ 🔘 📛, 🖼 "💳" 🔜 🎦 🦁 (**💳**) & "🎀" 🔜 🎦 ❕ (_🎀_). + +!!! tip + 👆 🚫 ✔️ 🚮 🗃 🌐 🔖 👈 👆 ⚙️. + +### ⚙️ 👆 🔖 + +⚙️ `tags` 🔢 ⏮️ 👆 *➡ 🛠️* (& `APIRouter`Ⓜ) 🛠️ 👫 🎏 🔖: + +```Python hl_lines="21 26" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +!!! info + ✍ 🌅 🔃 🔖 [➡ 🛠️ 📳](../path-operation-configuration/#tags){.internal-link target=_blank}. + +### ✅ 🩺 + +🔜, 🚥 👆 ✅ 🩺, 👫 🔜 🎦 🌐 🌖 🗃: + + + +### ✔ 🔖 + +✔ 🔠 🔖 🗃 📖 🔬 ✔ 🎦 🩺 🎚. + +🖼, ✋️ `users` 🔜 🚶 ⏮️ `items` 🔤 ✔, ⚫️ 🎦 ⏭ 👫, ↩️ 👥 🚮 👫 🗃 🥇 📖 📇. + +## 🗄 📛 + +🔢, 🗄 🔗 🍦 `/openapi.json`. + +✋️ 👆 💪 🔗 ⚫️ ⏮️ 🔢 `openapi_url`. + +🖼, ⚒ ⚫️ 🍦 `/api/v1/openapi.json`: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial002.py!} +``` + +🚥 👆 💚 ❎ 🗄 🔗 🍕 👆 💪 ⚒ `openapi_url=None`, 👈 🔜 ❎ 🧾 👩‍💻 🔢 👈 ⚙️ ⚫️. + +## 🩺 📛 + +👆 💪 🔗 2️⃣ 🧾 👩‍💻 🔢 🔌: + +* **🦁 🎚**: 🍦 `/docs`. + * 👆 💪 ⚒ 🚮 📛 ⏮️ 🔢 `docs_url`. + * 👆 💪 ❎ ⚫️ ⚒ `docs_url=None`. +* **📄**: 🍦 `/redoc`. + * 👆 💪 ⚒ 🚮 📛 ⏮️ 🔢 `redoc_url`. + * 👆 💪 ❎ ⚫️ ⚒ `redoc_url=None`. + +🖼, ⚒ 🦁 🎚 🍦 `/documentation` & ❎ 📄: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial003.py!} +``` diff --git a/docs/em/docs/tutorial/middleware.md b/docs/em/docs/tutorial/middleware.md new file mode 100644 index 0000000000..644b4690c1 --- /dev/null +++ b/docs/em/docs/tutorial/middleware.md @@ -0,0 +1,61 @@ +# 🛠️ + +👆 💪 🚮 🛠️ **FastAPI** 🈸. + +"🛠️" 🔢 👈 👷 ⏮️ 🔠 **📨** ⏭ ⚫️ 🛠️ 🙆 🎯 *➡ 🛠️*. & ⏮️ 🔠 **📨** ⏭ 🛬 ⚫️. + +* ⚫️ ✊ 🔠 **📨** 👈 👟 👆 🈸. +* ⚫️ 💪 ⤴️ 🕳 👈 **📨** ⚖️ 🏃 🙆 💪 📟. +* ⤴️ ⚫️ 🚶‍♀️ **📨** 🛠️ 🎂 🈸 ( *➡ 🛠️*). +* ⚫️ ⤴️ ✊ **📨** 🏗 🈸 ( *➡ 🛠️*). +* ⚫️ 💪 🕳 👈 **📨** ⚖️ 🏃 🙆 💪 📟. +* ⤴️ ⚫️ 📨 **📨**. + +!!! note "📡 ℹ" + 🚥 👆 ✔️ 🔗 ⏮️ `yield`, 🚪 📟 🔜 🏃 *⏮️* 🛠️. + + 🚥 📤 🙆 🖥 📋 (📄 ⏪), 👫 🔜 🏃 *⏮️* 🌐 🛠️. + +## ✍ 🛠️ + +✍ 🛠️ 👆 ⚙️ 👨‍🎨 `@app.middleware("http")` 🔛 🔝 🔢. + +🛠️ 🔢 📨: + +* `request`. +* 🔢 `call_next` 👈 🔜 📨 `request` 🔢. + * 👉 🔢 🔜 🚶‍♀️ `request` 🔗 *➡ 🛠️*. + * ⤴️ ⚫️ 📨 `response` 🏗 🔗 *➡ 🛠️*. +* 👆 💪 ⤴️ 🔀 🌅 `response` ⏭ 🛬 ⚫️. + +```Python hl_lines="8-9 11 14" +{!../../../docs_src/middleware/tutorial001.py!} +``` + +!!! tip + ✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡. + + ✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 ([⚜ (✖️-🇨🇳 ℹ 🤝)](cors.md){.internal-link target=_blank}) ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.requests import Request`. + + **FastAPI** 🚚 ⚫️ 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. + +### ⏭ & ⏮️ `response` + +👆 💪 🚮 📟 🏃 ⏮️ `request`, ⏭ 🙆 *➡ 🛠️* 📨 ⚫️. + +& ⏮️ `response` 🏗, ⏭ 🛬 ⚫️. + +🖼, 👆 💪 🚮 🛃 🎚 `X-Process-Time` ⚗ 🕰 🥈 👈 ⚫️ ✊ 🛠️ 📨 & 🏗 📨: + +```Python hl_lines="10 12-13" +{!../../../docs_src/middleware/tutorial001.py!} +``` + +## 🎏 🛠️ + +👆 💪 ⏪ ✍ 🌖 🔃 🎏 🛠️ [🏧 👩‍💻 🦮: 🏧 🛠️](../advanced/middleware.md){.internal-link target=_blank}. + +👆 🔜 ✍ 🔃 ❔ 🍵 ⏮️ 🛠️ ⏭ 📄. diff --git a/docs/em/docs/tutorial/path-operation-configuration.md b/docs/em/docs/tutorial/path-operation-configuration.md new file mode 100644 index 0000000000..916529258d --- /dev/null +++ b/docs/em/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,179 @@ +# ➡ 🛠️ 📳 + +📤 📚 🔢 👈 👆 💪 🚶‍♀️ 👆 *➡ 🛠️ 👨‍🎨* 🔗 ⚫️. + +!!! warning + 👀 👈 👫 🔢 🚶‍♀️ 🔗 *➡ 🛠️ 👨‍🎨*, 🚫 👆 *➡ 🛠️ 🔢*. + +## 📨 👔 📟 + +👆 💪 🔬 (🇺🇸🔍) `status_code` ⚙️ 📨 👆 *➡ 🛠️*. + +👆 💪 🚶‍♀️ 🔗 `int` 📟, 💖 `404`. + +✋️ 🚥 👆 🚫 💭 ⚫️❔ 🔠 🔢 📟, 👆 💪 ⚙️ ⌨ 📉 `status`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1 15" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} + ``` + +👈 👔 📟 🔜 ⚙️ 📨 & 🔜 🚮 🗄 🔗. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette import status`. + + **FastAPI** 🚚 🎏 `starlette.status` `fastapi.status` 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. + +## 🔖 + +👆 💪 🚮 🔖 👆 *➡ 🛠️*, 🚶‍♀️ 🔢 `tags` ⏮️ `list` `str` (🛎 1️⃣ `str`): + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="15 20 25" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} + ``` + +👫 🔜 🚮 🗄 🔗 & ⚙️ 🏧 🧾 🔢: + + + +### 🔖 ⏮️ 🔢 + +🚥 👆 ✔️ 🦏 🈸, 👆 5️⃣📆 🔚 🆙 📈 **📚 🔖**, & 👆 🔜 💚 ⚒ 💭 👆 🕧 ⚙️ **🎏 🔖** 🔗 *➡ 🛠️*. + +👫 💼, ⚫️ 💪 ⚒ 🔑 🏪 🔖 `Enum`. + +**FastAPI** 🐕‍🦺 👈 🎏 🌌 ⏮️ ✅ 🎻: + +```Python hl_lines="1 8-10 13 18" +{!../../../docs_src/path_operation_configuration/tutorial002b.py!} +``` + +## 📄 & 📛 + +👆 💪 🚮 `summary` & `description`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} + ``` + +## 📛 ⚪️➡️ #️⃣ + +📛 😑 📏 & 📔 💗 ⏸, 👆 💪 📣 *➡ 🛠️* 📛 🔢 #️⃣ & **FastAPI** 🔜 ✍ ⚫️ ⚪️➡️ 📤. + +👆 💪 ✍ #️⃣ , ⚫️ 🔜 🔬 & 🖥 ☑ (✊ 🔘 🏧 #️⃣ 📐). + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17-25" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} + ``` + +⚫️ 🔜 ⚙️ 🎓 🩺: + + + +## 📨 📛 + +👆 💪 ✔ 📨 📛 ⏮️ 🔢 `response_description`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="19" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} + ``` + +!!! info + 👀 👈 `response_description` 🔗 🎯 📨, `description` 🔗 *➡ 🛠️* 🏢. + +!!! check + 🗄 ✔ 👈 🔠 *➡ 🛠️* 🚚 📨 📛. + + , 🚥 👆 🚫 🚚 1️⃣, **FastAPI** 🔜 🔁 🏗 1️⃣ "🏆 📨". + + + +## 😢 *➡ 🛠️* + +🚥 👆 💪 ™ *➡ 🛠️* 😢, ✋️ 🍵 ❎ ⚫️, 🚶‍♀️ 🔢 `deprecated`: + +```Python hl_lines="16" +{!../../../docs_src/path_operation_configuration/tutorial006.py!} +``` + +⚫️ 🔜 🎯 ™ 😢 🎓 🩺: + + + +✅ ❔ 😢 & 🚫-😢 *➡ 🛠️* 👀 💖: + + + +## 🌃 + +👆 💪 🔗 & 🚮 🗃 👆 *➡ 🛠️* 💪 🚶‍♀️ 🔢 *➡ 🛠️ 👨‍🎨*. diff --git a/docs/em/docs/tutorial/path-params-numeric-validations.md b/docs/em/docs/tutorial/path-params-numeric-validations.md new file mode 100644 index 0000000000..b1ba2670b6 --- /dev/null +++ b/docs/em/docs/tutorial/path-params-numeric-validations.md @@ -0,0 +1,138 @@ +# ➡ 🔢 & 🔢 🔬 + +🎏 🌌 👈 👆 💪 📣 🌅 🔬 & 🗃 🔢 🔢 ⏮️ `Query`, 👆 💪 📣 🎏 🆎 🔬 & 🗃 ➡ 🔢 ⏮️ `Path`. + +## 🗄 ➡ + +🥇, 🗄 `Path` ⚪️➡️ `fastapi`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} + ``` + +## 📣 🗃 + +👆 💪 📣 🌐 🎏 🔢 `Query`. + +🖼, 📣 `title` 🗃 💲 ➡ 🔢 `item_id` 👆 💪 🆎: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="8" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} + ``` + +!!! note + ➡ 🔢 🕧 ✔ ⚫️ ✔️ 🍕 ➡. + + , 👆 🔜 📣 ⚫️ ⏮️ `...` ™ ⚫️ ✔. + + 👐, 🚥 👆 📣 ⚫️ ⏮️ `None` ⚖️ ⚒ 🔢 💲, ⚫️ 🔜 🚫 📉 🕳, ⚫️ 🔜 🕧 🚚. + +## ✔ 🔢 👆 💪 + +➡️ 💬 👈 👆 💚 📣 🔢 🔢 `q` ✔ `str`. + +& 👆 🚫 💪 📣 🕳 🙆 👈 🔢, 👆 🚫 🤙 💪 ⚙️ `Query`. + +✋️ 👆 💪 ⚙️ `Path` `item_id` ➡ 🔢. + +🐍 🔜 😭 🚥 👆 🚮 💲 ⏮️ "🔢" ⏭ 💲 👈 🚫 ✔️ "🔢". + +✋️ 👆 💪 🏤-✔ 👫, & ✔️ 💲 🍵 🔢 (🔢 🔢 `q`) 🥇. + +⚫️ 🚫 🤔 **FastAPI**. ⚫️ 🔜 🔍 🔢 👫 📛, 🆎 & 🔢 📄 (`Query`, `Path`, ♒️), ⚫️ 🚫 💅 🔃 ✔. + +, 👆 💪 📣 👆 🔢: + +```Python hl_lines="7" +{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} +``` + +## ✔ 🔢 👆 💪, 🎱 + +🚥 👆 💚 📣 `q` 🔢 🔢 🍵 `Query` 🚫 🙆 🔢 💲, & ➡ 🔢 `item_id` ⚙️ `Path`, & ✔️ 👫 🎏 ✔, 🐍 ✔️ 🐥 🎁 ❕ 👈. + +🚶‍♀️ `*`, 🥇 🔢 🔢. + +🐍 🏆 🚫 🕳 ⏮️ 👈 `*`, ✋️ ⚫️ 🔜 💭 👈 🌐 📄 🔢 🔜 🤙 🇨🇻 ❌ (🔑-💲 👫), 💭 kwargs. 🚥 👫 🚫 ✔️ 🔢 💲. + +```Python hl_lines="7" +{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} +``` + +## 🔢 🔬: 👑 🌘 ⚖️ 🌓 + +⏮️ `Query` & `Path` (& 🎏 👆 🔜 👀 ⏪) 👆 💪 📣 🔢 ⚛. + +📥, ⏮️ `ge=1`, `item_id` 🔜 💪 🔢 🔢 "`g`🅾 🌘 ⚖️ `e`🅾" `1`. + +```Python hl_lines="8" +{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} +``` + +## 🔢 🔬: 🌘 🌘 & 🌘 🌘 ⚖️ 🌓 + +🎏 ✔: + +* `gt`: `g`🅾 `t`👲 +* `le`: `l`👭 🌘 ⚖️ `e`🅾 + +```Python hl_lines="9" +{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} +``` + +## 🔢 🔬: 🎈, 🌘 🌘 & 🌘 🌘 + +🔢 🔬 👷 `float` 💲. + +📥 🌐❔ ⚫️ ▶️️ ⚠ 💪 📣 gt & 🚫 ge. ⏮️ ⚫️ 👆 💪 🚚, 🖼, 👈 💲 🔜 👑 🌘 `0`, 🚥 ⚫️ 🌘 🌘 `1`. + +, `0.5` 🔜 ☑ 💲. ✋️ `0.0` ⚖️ `0` 🔜 🚫. + +& 🎏 lt. + +```Python hl_lines="11" +{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} +``` + +## 🌃 + +⏮️ `Query`, `Path` (& 🎏 👆 🚫 👀) 👆 💪 📣 🗃 & 🎻 🔬 🎏 🌌 ⏮️ [🔢 🔢 & 🎻 🔬](query-params-str-validations.md){.internal-link target=_blank}. + +& 👆 💪 📣 🔢 🔬: + +* `gt`: `g`🅾 `t`👲 +* `ge`: `g`🅾 🌘 ⚖️ `e`🅾 +* `lt`: `l`👭 `t`👲 +* `le`: `l`👭 🌘 ⚖️ `e`🅾 + +!!! info + `Query`, `Path`, & 🎏 🎓 👆 🔜 👀 ⏪ 🏿 ⚠ `Param` 🎓. + + 🌐 👫 💰 🎏 🔢 🌖 🔬 & 🗃 👆 ✔️ 👀. + +!!! note "📡 ℹ" + 🕐❔ 👆 🗄 `Query`, `Path` & 🎏 ⚪️➡️ `fastapi`, 👫 🤙 🔢. + + 👈 🕐❔ 🤙, 📨 👐 🎓 🎏 📛. + + , 👆 🗄 `Query`, ❔ 🔢. & 🕐❔ 👆 🤙 ⚫️, ⚫️ 📨 👐 🎓 🌟 `Query`. + + 👫 🔢 📤 (↩️ ⚙️ 🎓 🔗) 👈 👆 👨‍🎨 🚫 ™ ❌ 🔃 👫 🆎. + + 👈 🌌 👆 💪 ⚙️ 👆 😐 👨‍🎨 & 🛠️ 🧰 🍵 ✔️ 🚮 🛃 📳 🤷‍♂ 📚 ❌. diff --git a/docs/em/docs/tutorial/path-params.md b/docs/em/docs/tutorial/path-params.md new file mode 100644 index 0000000000..ea939b458a --- /dev/null +++ b/docs/em/docs/tutorial/path-params.md @@ -0,0 +1,252 @@ +# ➡ 🔢 + +👆 💪 📣 ➡ "🔢" ⚖️ "🔢" ⏮️ 🎏 ❕ ⚙️ 🐍 📁 🎻: + +```Python hl_lines="6-7" +{!../../../docs_src/path_params/tutorial001.py!} +``` + +💲 ➡ 🔢 `item_id` 🔜 🚶‍♀️ 👆 🔢 ❌ `item_id`. + +, 🚥 👆 🏃 👉 🖼 & 🚶 http://127.0.0.1:8000/items/foo, 👆 🔜 👀 📨: + +```JSON +{"item_id":"foo"} +``` + +## ➡ 🔢 ⏮️ 🆎 + +👆 💪 📣 🆎 ➡ 🔢 🔢, ⚙️ 🐩 🐍 🆎 ✍: + +```Python hl_lines="7" +{!../../../docs_src/path_params/tutorial002.py!} +``` + +👉 💼, `item_id` 📣 `int`. + +!!! check + 👉 🔜 🤝 👆 👨‍🎨 🐕‍🦺 🔘 👆 🔢, ⏮️ ❌ ✅, 🛠️, ♒️. + +## 💽 🛠️ + +🚥 👆 🏃 👉 🖼 & 📂 👆 🖥 http://127.0.0.1:8000/items/3, 👆 🔜 👀 📨: + +```JSON +{"item_id":3} +``` + +!!! check + 👀 👈 💲 👆 🔢 📨 (& 📨) `3`, 🐍 `int`, 🚫 🎻 `"3"`. + + , ⏮️ 👈 🆎 📄, **FastAPI** 🤝 👆 🏧 📨 "✍". + +## 💽 🔬 + +✋️ 🚥 👆 🚶 🖥 http://127.0.0.1:8000/items/foo, 👆 🔜 👀 👌 🇺🇸🔍 ❌: + +```JSON +{ + "detail": [ + { + "loc": [ + "path", + "item_id" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +↩️ ➡ 🔢 `item_id` ✔️ 💲 `"foo"`, ❔ 🚫 `int`. + +🎏 ❌ 🔜 😑 🚥 👆 🚚 `float` ↩️ `int`,: http://127.0.0.1:8000/items/4.2 + +!!! check + , ⏮️ 🎏 🐍 🆎 📄, **FastAPI** 🤝 👆 💽 🔬. + + 👀 👈 ❌ 🎯 🇵🇸 ⚫️❔ ☝ 🌐❔ 🔬 🚫 🚶‍♀️. + + 👉 🙃 👍 ⏪ 🛠️ & 🛠️ 📟 👈 🔗 ⏮️ 👆 🛠️. + +## 🧾 + +& 🕐❔ 👆 📂 👆 🖥 http://127.0.0.1:8000/docs, 👆 🔜 👀 🏧, 🎓, 🛠️ 🧾 💖: + + + +!!! check + 🔄, ⏮️ 👈 🎏 🐍 🆎 📄, **FastAPI** 🤝 👆 🏧, 🎓 🧾 (🛠️ 🦁 🎚). + + 👀 👈 ➡ 🔢 📣 🔢. + +## 🐩-⚓️ 💰, 🎛 🧾 + +& ↩️ 🏗 🔗 ⚪️➡️ 🗄 🐩, 📤 📚 🔗 🧰. + +↩️ 👉, **FastAPI** ⚫️ 🚚 🎛 🛠️ 🧾 (⚙️ 📄), ❔ 👆 💪 🔐 http://127.0.0.1:8000/redoc: + + + +🎏 🌌, 📤 📚 🔗 🧰. ✅ 📟 ⚡ 🧰 📚 🇪🇸. + +## Pydantic + +🌐 💽 🔬 🎭 🔽 🚘 Pydantic, 👆 🤚 🌐 💰 ⚪️➡️ ⚫️. & 👆 💭 👆 👍 ✋. + +👆 💪 ⚙️ 🎏 🆎 📄 ⏮️ `str`, `float`, `bool` & 📚 🎏 🏗 📊 🆎. + +📚 👫 🔬 ⏭ 📃 🔰. + +## ✔ 🤔 + +🕐❔ 🏗 *➡ 🛠️*, 👆 💪 🔎 ⚠ 🌐❔ 👆 ✔️ 🔧 ➡. + +💖 `/users/me`, ➡️ 💬 👈 ⚫️ 🤚 📊 🔃 ⏮️ 👩‍💻. + +& ⤴️ 👆 💪 ✔️ ➡ `/users/{user_id}` 🤚 💽 🔃 🎯 👩‍💻 👩‍💻 🆔. + +↩️ *➡ 🛠️* 🔬 ✔, 👆 💪 ⚒ 💭 👈 ➡ `/users/me` 📣 ⏭ 1️⃣ `/users/{user_id}`: + +```Python hl_lines="6 11" +{!../../../docs_src/path_params/tutorial003.py!} +``` + +⏪, ➡ `/users/{user_id}` 🔜 🏏 `/users/me`, "💭" 👈 ⚫️ 📨 🔢 `user_id` ⏮️ 💲 `"me"`. + +➡, 👆 🚫🔜 ↔ ➡ 🛠️: + +```Python hl_lines="6 11" +{!../../../docs_src/path_params/tutorial003b.py!} +``` + +🥇 🕐 🔜 🕧 ⚙️ ↩️ ➡ 🏏 🥇. + +## 🔁 💲 + +🚥 👆 ✔️ *➡ 🛠️* 👈 📨 *➡ 🔢*, ✋️ 👆 💚 💪 ☑ *➡ 🔢* 💲 🔁, 👆 💪 ⚙️ 🐩 🐍 `Enum`. + +### ✍ `Enum` 🎓 + +🗄 `Enum` & ✍ 🎧-🎓 👈 😖 ⚪️➡️ `str` & ⚪️➡️ `Enum`. + +😖 ⚪️➡️ `str` 🛠️ 🩺 🔜 💪 💭 👈 💲 🔜 🆎 `string` & 🔜 💪 ✍ ☑. + +⤴️ ✍ 🎓 🔢 ⏮️ 🔧 💲, ❔ 🔜 💪 ☑ 💲: + +```Python hl_lines="1 6-9" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +!!! info + 🔢 (⚖️ 🔢) 💪 🐍 ↩️ ⏬ 3️⃣.4️⃣. + +!!! tip + 🚥 👆 💭, "📊", "🎓", & "🍏" 📛 🎰 🏫 🏷. + +### 📣 *➡ 🔢* + +⤴️ ✍ *➡ 🔢* ⏮️ 🆎 ✍ ⚙️ 🔢 🎓 👆 ✍ (`ModelName`): + +```Python hl_lines="16" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +### ✅ 🩺 + +↩️ 💪 💲 *➡ 🔢* 🔢, 🎓 🩺 💪 🎦 👫 🎆: + + + +### 👷 ⏮️ 🐍 *🔢* + +💲 *➡ 🔢* 🔜 *🔢 👨‍🎓*. + +#### 🔬 *🔢 👨‍🎓* + +👆 💪 🔬 ⚫️ ⏮️ *🔢 👨‍🎓* 👆 ✍ 🔢 `ModelName`: + +```Python hl_lines="17" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +#### 🤚 *🔢 💲* + +👆 💪 🤚 ☑ 💲 ( `str` 👉 💼) ⚙️ `model_name.value`, ⚖️ 🏢, `your_enum_member.value`: + +```Python hl_lines="20" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +!!! tip + 👆 💪 🔐 💲 `"lenet"` ⏮️ `ModelName.lenet.value`. + +#### 📨 *🔢 👨‍🎓* + +👆 💪 📨 *🔢 👨‍🎓* ⚪️➡️ 👆 *➡ 🛠️*, 🐦 🎻 💪 (✅ `dict`). + +👫 🔜 🗜 👫 🔗 💲 (🎻 👉 💼) ⏭ 🛬 👫 👩‍💻: + +```Python hl_lines="18 21 23" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +👆 👩‍💻 👆 🔜 🤚 🎻 📨 💖: + +```JSON +{ + "model_name": "alexnet", + "message": "Deep Learning FTW!" +} +``` + +## ➡ 🔢 ⚗ ➡ + +➡️ 💬 👆 ✔️ *➡ 🛠️* ⏮️ ➡ `/files/{file_path}`. + +✋️ 👆 💪 `file_path` ⚫️ 🔌 *➡*, 💖 `home/johndoe/myfile.txt`. + +, 📛 👈 📁 🔜 🕳 💖: `/files/home/johndoe/myfile.txt`. + +### 🗄 🐕‍🦺 + +🗄 🚫 🐕‍🦺 🌌 📣 *➡ 🔢* 🔌 *➡* 🔘, 👈 💪 ↘️ 😐 👈 ⚠ 💯 & 🔬. + +👐, 👆 💪 ⚫️ **FastAPI**, ⚙️ 1️⃣ 🔗 🧰 ⚪️➡️ 💃. + +& 🩺 🔜 👷, 👐 🚫 ❎ 🙆 🧾 💬 👈 🔢 🔜 🔌 ➡. + +### ➡ 🔌 + +⚙️ 🎛 🔗 ⚪️➡️ 💃 👆 💪 📣 *➡ 🔢* ⚗ *➡* ⚙️ 📛 💖: + +``` +/files/{file_path:path} +``` + +👉 💼, 📛 🔢 `file_path`, & 🏁 🍕, `:path`, 💬 ⚫️ 👈 🔢 🔜 🏏 🙆 *➡*. + +, 👆 💪 ⚙️ ⚫️ ⏮️: + +```Python hl_lines="6" +{!../../../docs_src/path_params/tutorial004.py!} +``` + +!!! tip + 👆 💪 💪 🔢 🔌 `/home/johndoe/myfile.txt`, ⏮️ 🏁 🔪 (`/`). + + 👈 💼, 📛 🔜: `/files//home/johndoe/myfile.txt`, ⏮️ 2️⃣✖️ 🔪 (`//`) 🖖 `files` & `home`. + +## 🌃 + +⏮️ **FastAPI**, ⚙️ 📏, 🏋️ & 🐩 🐍 🆎 📄, 👆 🤚: + +* 👨‍🎨 🐕‍🦺: ❌ ✅, ✍, ♒️. +* 💽 "" +* 💽 🔬 +* 🛠️ ✍ & 🏧 🧾 + +& 👆 🕴 ✔️ 📣 👫 🕐. + +👈 🎲 👑 ⭐ 📈 **FastAPI** 🔬 🎛 🛠️ (↖️ ⚪️➡️ 🍣 🎭). diff --git a/docs/em/docs/tutorial/query-params-str-validations.md b/docs/em/docs/tutorial/query-params-str-validations.md new file mode 100644 index 0000000000..d6b67bd518 --- /dev/null +++ b/docs/em/docs/tutorial/query-params-str-validations.md @@ -0,0 +1,467 @@ +# 🔢 🔢 & 🎻 🔬 + +**FastAPI** ✔ 👆 📣 🌖 ℹ & 🔬 👆 🔢. + +➡️ ✊ 👉 🈸 🖼: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!} + ``` + +🔢 🔢 `q` 🆎 `Union[str, None]` (⚖️ `str | None` 🐍 3️⃣.1️⃣0️⃣), 👈 ⛓ 👈 ⚫️ 🆎 `str` ✋️ 💪 `None`, & 👐, 🔢 💲 `None`, FastAPI 🔜 💭 ⚫️ 🚫 ✔. + +!!! note + FastAPI 🔜 💭 👈 💲 `q` 🚫 ✔ ↩️ 🔢 💲 `= None`. + + `Union` `Union[str, None]` 🔜 ✔ 👆 👨‍🎨 🤝 👆 👍 🐕‍🦺 & 🔍 ❌. + +## 🌖 🔬 + +👥 🔜 🛠️ 👈 ✋️ `q` 📦, 🕐❔ ⚫️ 🚚, **🚮 📐 🚫 📉 5️⃣0️⃣ 🦹**. + +### 🗄 `Query` + +🏆 👈, 🥇 🗄 `Query` ⚪️➡️ `fastapi`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3" + {!> ../../../docs_src/query_params_str_validations/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1" + {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!} + ``` + +## ⚙️ `Query` 🔢 💲 + +& 🔜 ⚙️ ⚫️ 🔢 💲 👆 🔢, ⚒ 🔢 `max_length` 5️⃣0️⃣: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!} + ``` + +👥 ✔️ ❎ 🔢 💲 `None` 🔢 ⏮️ `Query()`, 👥 💪 🔜 ⚒ 🔢 💲 ⏮️ 🔢 `Query(default=None)`, ⚫️ 🍦 🎏 🎯 ⚖ 👈 🔢 💲. + +: + +```Python +q: Union[str, None] = Query(default=None) +``` + +...⚒ 🔢 📦, 🎏: + +```Python +q: Union[str, None] = None +``` + +& 🐍 3️⃣.1️⃣0️⃣ & 🔛: + +```Python +q: str | None = Query(default=None) +``` + +...⚒ 🔢 📦, 🎏: + +```Python +q: str | None = None +``` + +✋️ ⚫️ 📣 ⚫️ 🎯 💆‍♂ 🔢 🔢. + +!!! info + ✔️ 🤯 👈 🌅 ⚠ 🍕 ⚒ 🔢 📦 🍕: + + ```Python + = None + ``` + + ⚖️: + + ```Python + = Query(default=None) + ``` + + ⚫️ 🔜 ⚙️ 👈 `None` 🔢 💲, & 👈 🌌 ⚒ 🔢 **🚫 ✔**. + + `Union[str, None]` 🍕 ✔ 👆 👨‍🎨 🚚 👻 🐕‍🦺, ✋️ ⚫️ 🚫 ⚫️❔ 💬 FastAPI 👈 👉 🔢 🚫 ✔. + +⤴️, 👥 💪 🚶‍♀️ 🌅 🔢 `Query`. 👉 💼, `max_length` 🔢 👈 ✔ 🎻: + +```Python +q: Union[str, None] = Query(default=None, max_length=50) +``` + +👉 🔜 ✔ 📊, 🎦 🆑 ❌ 🕐❔ 📊 🚫 ☑, & 📄 🔢 🗄 🔗 *➡ 🛠️*. + +## 🚮 🌅 🔬 + +👆 💪 🚮 🔢 `min_length`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!} + ``` + +## 🚮 🥔 🧬 + +👆 💪 🔬 🥔 🧬 👈 🔢 🔜 🏏: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial004_py310.py!} + ``` + +👉 🎯 🥔 🧬 ✅ 👈 📨 🔢 💲: + +* `^`: ▶️ ⏮️ 📄 🦹, 🚫 ✔️ 🦹 ⏭. +* `fixedquery`: ✔️ ☑ 💲 `fixedquery`. +* `$`: 🔚 📤, 🚫 ✔️ 🙆 🌖 🦹 ⏮️ `fixedquery`. + +🚥 👆 💭 💸 ⏮️ 🌐 👉 **"🥔 🧬"** 💭, 🚫 😟. 👫 🏋️ ❔ 📚 👫👫. 👆 💪 📚 💩 🍵 💆‍♂ 🥔 🧬. + +✋️ 🕐❔ 👆 💪 👫 & 🚶 & 💡 👫, 💭 👈 👆 💪 ⏪ ⚙️ 👫 🔗 **FastAPI**. + +## 🔢 💲 + +🎏 🌌 👈 👆 💪 🚶‍♀️ `None` 💲 `default` 🔢, 👆 💪 🚶‍♀️ 🎏 💲. + +➡️ 💬 👈 👆 💚 📣 `q` 🔢 🔢 ✔️ `min_length` `3`, & ✔️ 🔢 💲 `"fixedquery"`: + +```Python hl_lines="7" +{!../../../docs_src/query_params_str_validations/tutorial005.py!} +``` + +!!! note + ✔️ 🔢 💲 ⚒ 🔢 📦. + +## ⚒ ⚫️ ✔ + +🕐❔ 👥 🚫 💪 📣 🌅 🔬 ⚖️ 🗃, 👥 💪 ⚒ `q` 🔢 🔢 ✔ 🚫 📣 🔢 💲, 💖: + +```Python +q: str +``` + +↩️: + +```Python +q: Union[str, None] = None +``` + +✋️ 👥 🔜 📣 ⚫️ ⏮️ `Query`, 🖼 💖: + +```Python +q: Union[str, None] = Query(default=None, min_length=3) +``` + +, 🕐❔ 👆 💪 📣 💲 ✔ ⏪ ⚙️ `Query`, 👆 💪 🎯 🚫 📣 🔢 💲: + +```Python hl_lines="7" +{!../../../docs_src/query_params_str_validations/tutorial006.py!} +``` + +### ✔ ⏮️ ❕ (`...`) + +📤 🎛 🌌 🎯 📣 👈 💲 ✔. 👆 💪 ⚒ `default` 🔢 🔑 💲 `...`: + +```Python hl_lines="7" +{!../../../docs_src/query_params_str_validations/tutorial006b.py!} +``` + +!!! info + 🚥 👆 🚫 👀 👈 `...` ⏭: ⚫️ 🎁 👁 💲, ⚫️ 🍕 🐍 & 🤙 "❕". + + ⚫️ ⚙️ Pydantic & FastAPI 🎯 📣 👈 💲 ✔. + +👉 🔜 ➡️ **FastAPI** 💭 👈 👉 🔢 ✔. + +### ✔ ⏮️ `None` + +👆 💪 📣 👈 🔢 💪 🚫 `None`, ✋️ 👈 ⚫️ ✔. 👉 🔜 ⚡ 👩‍💻 📨 💲, 🚥 💲 `None`. + +👈, 👆 💪 📣 👈 `None` ☑ 🆎 ✋️ ⚙️ `default=...`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial006c.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial006c_py310.py!} + ``` + +!!! tip + Pydantic, ❔ ⚫️❔ 🏋️ 🌐 💽 🔬 & 🛠️ FastAPI, ✔️ 🎁 🎭 🕐❔ 👆 ⚙️ `Optional` ⚖️ `Union[Something, None]` 🍵 🔢 💲, 👆 💪 ✍ 🌅 🔃 ⚫️ Pydantic 🩺 🔃 ✔ 📦 🏑. + +### ⚙️ Pydantic `Required` ↩️ ❕ (`...`) + +🚥 👆 💭 😬 ⚙️ `...`, 👆 💪 🗄 & ⚙️ `Required` ⚪️➡️ Pydantic: + +```Python hl_lines="2 8" +{!../../../docs_src/query_params_str_validations/tutorial006d.py!} +``` + +!!! tip + 💭 👈 🌅 💼, 🕐❔ 🕳 🚚, 👆 💪 🎯 🚫 `default` 🔢, 👆 🛎 🚫 ✔️ ⚙️ `...` 🚫 `Required`. + +## 🔢 🔢 📇 / 💗 💲 + +🕐❔ 👆 🔬 🔢 🔢 🎯 ⏮️ `Query` 👆 💪 📣 ⚫️ 📨 📇 💲, ⚖️ 🙆‍♀ 🎏 🌌, 📨 💗 💲. + +🖼, 📣 🔢 🔢 `q` 👈 💪 😑 💗 🕰 📛, 👆 💪 ✍: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!} + ``` + +⤴️, ⏮️ 📛 💖: + +``` +http://localhost:8000/items/?q=foo&q=bar +``` + +👆 🔜 📨 💗 `q` *🔢 🔢'* 💲 (`foo` & `bar`) 🐍 `list` 🔘 👆 *➡ 🛠️ 🔢*, *🔢 🔢* `q`. + +, 📨 👈 📛 🔜: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +!!! tip + 📣 🔢 🔢 ⏮️ 🆎 `list`, 💖 🖼 🔛, 👆 💪 🎯 ⚙️ `Query`, ⏪ ⚫️ 🔜 🔬 📨 💪. + +🎓 🛠️ 🩺 🔜 ℹ ➡️, ✔ 💗 💲: + + + +### 🔢 🔢 📇 / 💗 💲 ⏮️ 🔢 + +& 👆 💪 🔬 🔢 `list` 💲 🚥 👌 🚚: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial012.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!} + ``` + +🚥 👆 🚶: + +``` +http://localhost:8000/items/ +``` + +🔢 `q` 🔜: `["foo", "bar"]` & 👆 📨 🔜: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +#### ⚙️ `list` + +👆 💪 ⚙️ `list` 🔗 ↩️ `List[str]` (⚖️ `list[str]` 🐍 3️⃣.9️⃣ ➕): + +```Python hl_lines="7" +{!../../../docs_src/query_params_str_validations/tutorial013.py!} +``` + +!!! note + ✔️ 🤯 👈 👉 💼, FastAPI 🏆 🚫 ✅ 🎚 📇. + + 🖼, `List[int]` 🔜 ✅ (& 📄) 👈 🎚 📇 🔢. ✋️ `list` 😞 🚫🔜. + +## 📣 🌅 🗃 + +👆 💪 🚮 🌅 ℹ 🔃 🔢. + +👈 ℹ 🔜 🔌 🏗 🗄 & ⚙️ 🧾 👩‍💻 🔢 & 🔢 🧰. + +!!! note + ✔️ 🤯 👈 🎏 🧰 5️⃣📆 ✔️ 🎏 🎚 🗄 🐕‍🦺. + + 👫 💪 🚫 🎦 🌐 ➕ ℹ 📣, 👐 🌅 💼, ❌ ⚒ ⏪ 📄 🛠️. + +👆 💪 🚮 `title`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial007.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!} + ``` + +& `description`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="13" + {!> ../../../docs_src/query_params_str_validations/tutorial008.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="12" + {!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!} + ``` + +## 📛 🔢 + +🌈 👈 👆 💚 🔢 `item-query`. + +💖: + +``` +http://127.0.0.1:8000/items/?item-query=foobaritems +``` + +✋️ `item-query` 🚫 ☑ 🐍 🔢 📛. + +🔐 🔜 `item_query`. + +✋️ 👆 💪 ⚫️ ⚫️❔ `item-query`... + +⤴️ 👆 💪 📣 `alias`, & 👈 📛 ⚫️❔ 🔜 ⚙️ 🔎 🔢 💲: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial009.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!} + ``` + +## 😛 🔢 + +🔜 ➡️ 💬 👆 🚫 💖 👉 🔢 🚫🔜. + +👆 ✔️ 👈 ⚫️ 📤 ⏪ ↩️ 📤 👩‍💻 ⚙️ ⚫️, ✋️ 👆 💚 🩺 🎯 🎦 ⚫️ 😢. + +⤴️ 🚶‍♀️ 🔢 `deprecated=True` `Query`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/query_params_str_validations/tutorial010.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17" + {!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!} + ``` + +🩺 🔜 🎦 ⚫️ 💖 👉: + + + +## 🚫 ⚪️➡️ 🗄 + +🚫 🔢 🔢 ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚒ 🔢 `include_in_schema` `Query` `False`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial014.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!} + ``` + +## 🌃 + +👆 💪 📣 🌖 🔬 & 🗃 👆 🔢. + +💊 🔬 & 🗃: + +* `alias` +* `title` +* `description` +* `deprecated` + +🔬 🎯 🎻: + +* `min_length` +* `max_length` +* `regex` + +👫 🖼 👆 👀 ❔ 📣 🔬 `str` 💲. + +👀 ⏭ 📃 👀 ❔ 📣 🔬 🎏 🆎, 💖 🔢. diff --git a/docs/em/docs/tutorial/query-params.md b/docs/em/docs/tutorial/query-params.md new file mode 100644 index 0000000000..ccb235c151 --- /dev/null +++ b/docs/em/docs/tutorial/query-params.md @@ -0,0 +1,225 @@ +# 🔢 🔢 + +🕐❔ 👆 📣 🎏 🔢 🔢 👈 🚫 🍕 ➡ 🔢, 👫 🔁 🔬 "🔢" 🔢. + +```Python hl_lines="9" +{!../../../docs_src/query_params/tutorial001.py!} +``` + +🔢 ⚒ 🔑-💲 👫 👈 🚶 ⏮️ `?` 📛, 🎏 `&` 🦹. + +🖼, 📛: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +...🔢 🔢: + +* `skip`: ⏮️ 💲 `0` +* `limit`: ⏮️ 💲 `10` + +👫 🍕 📛, 👫 "🛎" 🎻. + +✋️ 🕐❔ 👆 📣 👫 ⏮️ 🐍 🆎 (🖼 🔛, `int`), 👫 🗜 👈 🆎 & ✔ 🛡 ⚫️. + +🌐 🎏 🛠️ 👈 ⚖ ➡ 🔢 ✔ 🔢 🔢: + +* 👨‍🎨 🐕‍🦺 (🎲) +* 💽 "✍" +* 💽 🔬 +* 🏧 🧾 + +## 🔢 + +🔢 🔢 🚫 🔧 🍕 ➡, 👫 💪 📦 & 💪 ✔️ 🔢 💲. + +🖼 🔛 👫 ✔️ 🔢 💲 `skip=0` & `limit=10`. + +, 🔜 📛: + +``` +http://127.0.0.1:8000/items/ +``` + +🔜 🎏 🔜: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +✋️ 🚥 👆 🚶, 🖼: + +``` +http://127.0.0.1:8000/items/?skip=20 +``` + +🔢 💲 👆 🔢 🔜: + +* `skip=20`: ↩️ 👆 ⚒ ⚫️ 📛 +* `limit=10`: ↩️ 👈 🔢 💲 + +## 📦 🔢 + +🎏 🌌, 👆 💪 📣 📦 🔢 🔢, ⚒ 👫 🔢 `None`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params/tutorial002_py310.py!} + ``` + +👉 💼, 🔢 🔢 `q` 🔜 📦, & 🔜 `None` 🔢. + +!!! check + 👀 👈 **FastAPI** 🙃 🥃 👀 👈 ➡ 🔢 `item_id` ➡ 🔢 & `q` 🚫,, ⚫️ 🔢 🔢. + +## 🔢 🔢 🆎 🛠️ + +👆 💪 📣 `bool` 🆎, & 👫 🔜 🗜: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params/tutorial003_py310.py!} + ``` + +👉 💼, 🚥 👆 🚶: + +``` +http://127.0.0.1:8000/items/foo?short=1 +``` + +⚖️ + +``` +http://127.0.0.1:8000/items/foo?short=True +``` + +⚖️ + +``` +http://127.0.0.1:8000/items/foo?short=true +``` + +⚖️ + +``` +http://127.0.0.1:8000/items/foo?short=on +``` + +⚖️ + +``` +http://127.0.0.1:8000/items/foo?short=yes +``` + +⚖️ 🙆 🎏 💼 📈 (🔠, 🥇 🔤 🔠, ♒️), 👆 🔢 🔜 👀 🔢 `short` ⏮️ `bool` 💲 `True`. ⏪ `False`. + + +## 💗 ➡ & 🔢 🔢 + +👆 💪 📣 💗 ➡ 🔢 & 🔢 🔢 🎏 🕰, **FastAPI** 💭 ❔ ❔. + +& 👆 🚫 ✔️ 📣 👫 🙆 🎯 ✔. + +👫 🔜 🔬 📛: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="8 10" + {!> ../../../docs_src/query_params/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="6 8" + {!> ../../../docs_src/query_params/tutorial004_py310.py!} + ``` + +## ✔ 🔢 🔢 + +🕐❔ 👆 📣 🔢 💲 🚫-➡ 🔢 (🔜, 👥 ✔️ 🕴 👀 🔢 🔢), ⤴️ ⚫️ 🚫 ✔. + +🚥 👆 🚫 💚 🚮 🎯 💲 ✋️ ⚒ ⚫️ 📦, ⚒ 🔢 `None`. + +✋️ 🕐❔ 👆 💚 ⚒ 🔢 🔢 ✔, 👆 💪 🚫 📣 🙆 🔢 💲: + +```Python hl_lines="6-7" +{!../../../docs_src/query_params/tutorial005.py!} +``` + +📥 🔢 🔢 `needy` ✔ 🔢 🔢 🆎 `str`. + +🚥 👆 📂 👆 🖥 📛 💖: + +``` +http://127.0.0.1:8000/items/foo-item +``` + +...🍵 ❎ ✔ 🔢 `needy`, 👆 🔜 👀 ❌ 💖: + +```JSON +{ + "detail": [ + { + "loc": [ + "query", + "needy" + ], + "msg": "field required", + "type": "value_error.missing" + } + ] +} +``` + +`needy` 🚚 🔢, 👆 🔜 💪 ⚒ ⚫️ 📛: + +``` +http://127.0.0.1:8000/items/foo-item?needy=sooooneedy +``` + +...👉 🔜 👷: + +```JSON +{ + "item_id": "foo-item", + "needy": "sooooneedy" +} +``` + +& ↗️, 👆 💪 🔬 🔢 ✔, ✔️ 🔢 💲, & 🍕 📦: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params/tutorial006.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params/tutorial006_py310.py!} + ``` + +👉 💼, 📤 3️⃣ 🔢 🔢: + +* `needy`, ✔ `str`. +* `skip`, `int` ⏮️ 🔢 💲 `0`. +* `limit`, 📦 `int`. + +!!! tip + 👆 💪 ⚙️ `Enum`Ⓜ 🎏 🌌 ⏮️ [➡ 🔢](path-params.md#predefined-values){.internal-link target=_blank}. diff --git a/docs/em/docs/tutorial/request-files.md b/docs/em/docs/tutorial/request-files.md new file mode 100644 index 0000000000..26631823f2 --- /dev/null +++ b/docs/em/docs/tutorial/request-files.md @@ -0,0 +1,186 @@ +# 📨 📁 + +👆 💪 🔬 📁 📂 👩‍💻 ⚙️ `File`. + +!!! info + 📨 📂 📁, 🥇 ❎ `python-multipart`. + + 🤶 Ⓜ. `pip install python-multipart`. + + 👉 ↩️ 📂 📁 📨 "📨 💽". + +## 🗄 `File` + +🗄 `File` & `UploadFile` ⚪️➡️ `fastapi`: + +```Python hl_lines="1" +{!../../../docs_src/request_files/tutorial001.py!} +``` + +## 🔬 `File` 🔢 + +✍ 📁 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Form`: + +```Python hl_lines="7" +{!../../../docs_src/request_files/tutorial001.py!} +``` + +!!! info + `File` 🎓 👈 😖 🔗 ⚪️➡️ `Form`. + + ✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `File` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. + +!!! tip + 📣 📁 💪, 👆 💪 ⚙️ `File`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢 ⚖️ 💪 (🎻) 🔢. + +📁 🔜 📂 "📨 💽". + +🚥 👆 📣 🆎 👆 *➡ 🛠️ 🔢* 🔢 `bytes`, **FastAPI** 🔜 ✍ 📁 👆 & 👆 🔜 📨 🎚 `bytes`. + +✔️ 🤯 👈 👉 ⛓ 👈 🎂 🎚 🔜 🏪 💾. 👉 🔜 👷 👍 🤪 📁. + +✋️ 📤 📚 💼 ❔ 👆 💪 💰 ⚪️➡️ ⚙️ `UploadFile`. + +## 📁 🔢 ⏮️ `UploadFile` + +🔬 📁 🔢 ⏮️ 🆎 `UploadFile`: + +```Python hl_lines="12" +{!../../../docs_src/request_files/tutorial001.py!} +``` + +⚙️ `UploadFile` ✔️ 📚 📈 🤭 `bytes`: + +* 👆 🚫 ✔️ ⚙️ `File()` 🔢 💲 🔢. +* ⚫️ ⚙️ "🧵" 📁: + * 📁 🏪 💾 🆙 🔆 📐 📉, & ⏮️ 🚶‍♀️ 👉 📉 ⚫️ 🔜 🏪 💾. +* 👉 ⛓ 👈 ⚫️ 🔜 👷 👍 ⭕ 📁 💖 🖼, 📹, ⭕ 💱, ♒️. 🍵 😩 🌐 💾. +* 👆 💪 🤚 🗃 ⚪️➡️ 📂 📁. +* ⚫️ ✔️ 📁-💖 `async` 🔢. +* ⚫️ 🎦 ☑ 🐍 `SpooledTemporaryFile` 🎚 👈 👆 💪 🚶‍♀️ 🔗 🎏 🗃 👈 ⌛ 📁-💖 🎚. + +### `UploadFile` + +`UploadFile` ✔️ 📄 🔢: + +* `filename`: `str` ⏮️ ⏮️ 📁 📛 👈 📂 (✅ `myimage.jpg`). +* `content_type`: `str` ⏮️ 🎚 🆎 (📁 🆎 / 📻 🆎) (✅ `image/jpeg`). +* `file`: `SpooledTemporaryFile` ( 📁-💖 🎚). 👉 ☑ 🐍 📁 👈 👆 💪 🚶‍♀️ 🔗 🎏 🔢 ⚖️ 🗃 👈 ⌛ "📁-💖" 🎚. + +`UploadFile` ✔️ 📄 `async` 👩‍🔬. 👫 🌐 🤙 🔗 📁 👩‍🔬 🔘 (⚙️ 🔗 `SpooledTemporaryFile`). + +* `write(data)`: ✍ `data` (`str` ⚖️ `bytes`) 📁. +* `read(size)`: ✍ `size` (`int`) 🔢/🦹 📁. +* `seek(offset)`: 🚶 🔢 🧘 `offset` (`int`) 📁. + * 🤶 Ⓜ., `await myfile.seek(0)` 🔜 🚶 ▶️ 📁. + * 👉 ✴️ ⚠ 🚥 👆 🏃 `await myfile.read()` 🕐 & ⤴️ 💪 ✍ 🎚 🔄. +* `close()`: 🔐 📁. + +🌐 👫 👩‍🔬 `async` 👩‍🔬, 👆 💪 "⌛" 👫. + +🖼, 🔘 `async` *➡ 🛠️ 🔢* 👆 💪 🤚 🎚 ⏮️: + +```Python +contents = await myfile.read() +``` + +🚥 👆 🔘 😐 `def` *➡ 🛠️ 🔢*, 👆 💪 🔐 `UploadFile.file` 🔗, 🖼: + +```Python +contents = myfile.file.read() +``` + +!!! note "`async` 📡 ℹ" + 🕐❔ 👆 ⚙️ `async` 👩‍🔬, **FastAPI** 🏃 📁 👩‍🔬 🧵 & ⌛ 👫. + +!!! note "💃 📡 ℹ" + **FastAPI**'Ⓜ `UploadFile` 😖 🔗 ⚪️➡️ **💃**'Ⓜ `UploadFile`, ✋️ 🚮 💪 🍕 ⚒ ⚫️ 🔗 ⏮️ **Pydantic** & 🎏 🍕 FastAPI. + +## ⚫️❔ "📨 💽" + +🌌 🕸 📨 (`
`) 📨 💽 💽 🛎 ⚙️ "🎁" 🔢 👈 📊, ⚫️ 🎏 ⚪️➡️ 🎻. + +**FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻. + +!!! note "📡 ℹ" + 📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded` 🕐❔ ⚫️ 🚫 🔌 📁. + + ✋️ 🕐❔ 📨 🔌 📁, ⚫️ 🗜 `multipart/form-data`. 🚥 👆 ⚙️ `File`, **FastAPI** 🔜 💭 ⚫️ ✔️ 🤚 📁 ⚪️➡️ ☑ 🍕 💪. + + 🚥 👆 💚 ✍ 🌖 🔃 👉 🔢 & 📨 🏑, 👳 🏇 🕸 🩺 POST. + +!!! warning + 👆 💪 📣 💗 `File` & `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `multipart/form-data` ↩️ `application/json`. + + 👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️. + +## 📦 📁 📂 + +👆 💪 ⚒ 📁 📦 ⚙️ 🐩 🆎 ✍ & ⚒ 🔢 💲 `None`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9 17" + {!> ../../../docs_src/request_files/tutorial001_02.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7 14" + {!> ../../../docs_src/request_files/tutorial001_02_py310.py!} + ``` + +## `UploadFile` ⏮️ 🌖 🗃 + +👆 💪 ⚙️ `File()` ⏮️ `UploadFile`, 🖼, ⚒ 🌖 🗃: + +```Python hl_lines="13" +{!../../../docs_src/request_files/tutorial001_03.py!} +``` + +## 💗 📁 📂 + +⚫️ 💪 📂 📚 📁 🎏 🕰. + +👫 🔜 👨‍💼 🎏 "📨 🏑" 📨 ⚙️ "📨 💽". + +⚙️ 👈, 📣 📇 `bytes` ⚖️ `UploadFile`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10 15" + {!> ../../../docs_src/request_files/tutorial002.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="8 13" + {!> ../../../docs_src/request_files/tutorial002_py39.py!} + ``` + +👆 🔜 📨, 📣, `list` `bytes` ⚖️ `UploadFile`Ⓜ. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.responses import HTMLResponse`. + + **FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. + +### 💗 📁 📂 ⏮️ 🌖 🗃 + +& 🎏 🌌 ⏭, 👆 💪 ⚙️ `File()` ⚒ 🌖 🔢, `UploadFile`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/request_files/tutorial003.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="16" + {!> ../../../docs_src/request_files/tutorial003_py39.py!} + ``` + +## 🌃 + +⚙️ `File`, `bytes`, & `UploadFile` 📣 📁 📂 📨, 📨 📨 💽. diff --git a/docs/em/docs/tutorial/request-forms-and-files.md b/docs/em/docs/tutorial/request-forms-and-files.md new file mode 100644 index 0000000000..99aeca0003 --- /dev/null +++ b/docs/em/docs/tutorial/request-forms-and-files.md @@ -0,0 +1,35 @@ +# 📨 📨 & 📁 + +👆 💪 🔬 📁 & 📨 🏑 🎏 🕰 ⚙️ `File` & `Form`. + +!!! info + 📨 📂 📁 & /⚖️ 📨 📊, 🥇 ❎ `python-multipart`. + + 🤶 Ⓜ. `pip install python-multipart`. + +## 🗄 `File` & `Form` + +```Python hl_lines="1" +{!../../../docs_src/request_forms_and_files/tutorial001.py!} +``` + +## 🔬 `File` & `Form` 🔢 + +✍ 📁 & 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`: + +```Python hl_lines="8" +{!../../../docs_src/request_forms_and_files/tutorial001.py!} +``` + +📁 & 📨 🏑 🔜 📂 📨 📊 & 👆 🔜 📨 📁 & 📨 🏑. + +& 👆 💪 📣 📁 `bytes` & `UploadFile`. + +!!! warning + 👆 💪 📣 💗 `File` & `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `multipart/form-data` ↩️ `application/json`. + + 👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️. + +## 🌃 + +⚙️ `File` & `Form` 👯‍♂️ 🕐❔ 👆 💪 📨 💽 & 📁 🎏 📨. diff --git a/docs/em/docs/tutorial/request-forms.md b/docs/em/docs/tutorial/request-forms.md new file mode 100644 index 0000000000..fa74adae5d --- /dev/null +++ b/docs/em/docs/tutorial/request-forms.md @@ -0,0 +1,58 @@ +# 📨 💽 + +🕐❔ 👆 💪 📨 📨 🏑 ↩️ 🎻, 👆 💪 ⚙️ `Form`. + +!!! info + ⚙️ 📨, 🥇 ❎ `python-multipart`. + + 🤶 Ⓜ. `pip install python-multipart`. + +## 🗄 `Form` + +🗄 `Form` ⚪️➡️ `fastapi`: + +```Python hl_lines="1" +{!../../../docs_src/request_forms/tutorial001.py!} +``` + +## 🔬 `Form` 🔢 + +✍ 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`: + +```Python hl_lines="7" +{!../../../docs_src/request_forms/tutorial001.py!} +``` + +🖼, 1️⃣ 🌌 Oauth2️⃣ 🔧 💪 ⚙️ (🤙 "🔐 💧") ⚫️ ✔ 📨 `username` & `password` 📨 🏑. + +🔌 🚚 🏑 ⚫️❔ 📛 `username` & `password`, & 📨 📨 🏑, 🚫 🎻. + +⏮️ `Form` 👆 💪 📣 🎏 📳 ⏮️ `Body` (& `Query`, `Path`, `Cookie`), 🔌 🔬, 🖼, 📛 (✅ `user-name` ↩️ `username`), ♒️. + +!!! info + `Form` 🎓 👈 😖 🔗 ⚪️➡️ `Body`. + +!!! tip + 📣 📨 💪, 👆 💪 ⚙️ `Form` 🎯, ↩️ 🍵 ⚫️ 🔢 🔜 🔬 🔢 🔢 ⚖️ 💪 (🎻) 🔢. + +## 🔃 "📨 🏑" + +🌌 🕸 📨 (`
`) 📨 💽 💽 🛎 ⚙️ "🎁" 🔢 👈 📊, ⚫️ 🎏 ⚪️➡️ 🎻. + +**FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻. + +!!! note "📡 ℹ" + 📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded`. + + ✋️ 🕐❔ 📨 🔌 📁, ⚫️ 🗜 `multipart/form-data`. 👆 🔜 ✍ 🔃 🚚 📁 ⏭ 📃. + + 🚥 👆 💚 ✍ 🌖 🔃 👉 🔢 & 📨 🏑, 👳 🏇 🕸 🩺 POST. + +!!! warning + 👆 💪 📣 💗 `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `application/x-www-form-urlencoded` ↩️ `application/json`. + + 👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️. + +## 🌃 + +⚙️ `Form` 📣 📨 💽 🔢 🔢. diff --git a/docs/em/docs/tutorial/response-model.md b/docs/em/docs/tutorial/response-model.md new file mode 100644 index 0000000000..6ea4413f89 --- /dev/null +++ b/docs/em/docs/tutorial/response-model.md @@ -0,0 +1,481 @@ +# 📨 🏷 - 📨 🆎 + +👆 💪 📣 🆎 ⚙️ 📨 ✍ *➡ 🛠️ 🔢* **📨 🆎**. + +👆 💪 ⚙️ **🆎 ✍** 🎏 🌌 👆 🔜 🔢 💽 🔢 **🔢**, 👆 💪 ⚙️ Pydantic 🏷, 📇, 📖, 📊 💲 💖 🔢, 🎻, ♒️. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="18 23" + {!> ../../../docs_src/response_model/tutorial001_01.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="18 23" + {!> ../../../docs_src/response_model/tutorial001_01_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="16 21" + {!> ../../../docs_src/response_model/tutorial001_01_py310.py!} + ``` + +FastAPI 🔜 ⚙️ 👉 📨 🆎: + +* **✔** 📨 💽. + * 🚥 💽 ❌ (✅ 👆 ❌ 🏑), ⚫️ ⛓ 👈 *👆* 📱 📟 💔, 🚫 🛬 ⚫️❔ ⚫️ 🔜, & ⚫️ 🔜 📨 💽 ❌ ↩️ 🛬 ❌ 💽. 👉 🌌 👆 & 👆 👩‍💻 💪 🎯 👈 👫 🔜 📨 💽 & 💽 💠 📈. +* 🚮 **🎻 🔗** 📨, 🗄 *➡ 🛠️*. + * 👉 🔜 ⚙️ **🏧 🩺**. + * ⚫️ 🔜 ⚙️ 🏧 👩‍💻 📟 ⚡ 🧰. + +✋️ 🏆 🥈: + +* ⚫️ 🔜 **📉 & ⛽** 🔢 📊 ⚫️❔ 🔬 📨 🆎. + * 👉 ✴️ ⚠ **💂‍♂**, 👥 🔜 👀 🌅 👈 🔛. + +## `response_model` 🔢 + +📤 💼 🌐❔ 👆 💪 ⚖️ 💚 📨 💽 👈 🚫 ⚫️❔ ⚫️❔ 🆎 📣. + +🖼, 👆 💪 💚 **📨 📖** ⚖️ 💽 🎚, ✋️ **📣 ⚫️ Pydantic 🏷**. 👉 🌌 Pydantic 🏷 🔜 🌐 💽 🧾, 🔬, ♒️. 🎚 👈 👆 📨 (✅ 📖 ⚖️ 💽 🎚). + +🚥 👆 🚮 📨 🆎 ✍, 🧰 & 👨‍🎨 🔜 😭 ⏮️ (☑) ❌ 💬 👆 👈 👆 🔢 🛬 🆎 (✅#️⃣) 👈 🎏 ⚪️➡️ ⚫️❔ 👆 📣 (✅ Pydantic 🏷). + +📚 💼, 👆 💪 ⚙️ *➡ 🛠️ 👨‍🎨* 🔢 `response_model` ↩️ 📨 🆎. + +👆 💪 ⚙️ `response_model` 🔢 🙆 *➡ 🛠️*: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* ♒️. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001_py310.py!} + ``` + +!!! note + 👀 👈 `response_model` 🔢 "👨‍🎨" 👩‍🔬 (`get`, `post`, ♒️). 🚫 👆 *➡ 🛠️ 🔢*, 💖 🌐 🔢 & 💪. + +`response_model` 📨 🎏 🆎 👆 🔜 📣 Pydantic 🏷 🏑,, ⚫️ 💪 Pydantic 🏷, ✋️ ⚫️ 💪, ✅ `list` Pydantic 🏷, 💖 `List[Item]`. + +FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & **🗜 & ⛽ 🔢 📊** 🚮 🆎 📄. + +!!! tip + 🚥 👆 ✔️ ⚠ 🆎 ✅ 👆 👨‍🎨, ✍, ♒️, 👆 💪 📣 🔢 📨 🆎 `Any`. + + 👈 🌌 👆 💬 👨‍🎨 👈 👆 😫 🛬 🕳. ✋️ FastAPI 🔜 💽 🧾, 🔬, 🖥, ♒️. ⏮️ `response_model`. + +### `response_model` 📫 + +🚥 👆 📣 👯‍♂️ 📨 🆎 & `response_model`, `response_model` 🔜 ✊ 📫 & ⚙️ FastAPI. + +👉 🌌 👆 💪 🚮 ☑ 🆎 ✍ 👆 🔢 🕐❔ 👆 🛬 🆎 🎏 🌘 📨 🏷, ⚙️ 👨‍🎨 & 🧰 💖 ✍. & 👆 💪 ✔️ FastAPI 💽 🔬, 🧾, ♒️. ⚙️ `response_model`. + +👆 💪 ⚙️ `response_model=None` ❎ 🏗 📨 🏷 👈 *➡ 🛠️*, 👆 5️⃣📆 💪 ⚫️ 🚥 👆 ❎ 🆎 ✍ 👜 👈 🚫 ☑ Pydantic 🏑, 👆 🔜 👀 🖼 👈 1️⃣ 📄 🔛. + +## 📨 🎏 🔢 💽 + +📥 👥 📣 `UserIn` 🏷, ⚫️ 🔜 🔌 🔢 🔐: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9 11" + {!> ../../../docs_src/response_model/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7 9" + {!> ../../../docs_src/response_model/tutorial002_py310.py!} + ``` + +!!! info + ⚙️ `EmailStr`, 🥇 ❎ `email_validator`. + + 🤶 Ⓜ. `pip install email-validator` + ⚖️ `pip install pydantic[email]`. + +& 👥 ⚙️ 👉 🏷 📣 👆 🔢 & 🎏 🏷 📣 👆 🔢: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="18" + {!> ../../../docs_src/response_model/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="16" + {!> ../../../docs_src/response_model/tutorial002_py310.py!} + ``` + +🔜, 🕐❔ 🖥 🏗 👩‍💻 ⏮️ 🔐, 🛠️ 🔜 📨 🎏 🔐 📨. + +👉 💼, ⚫️ 💪 🚫 ⚠, ↩️ ⚫️ 🎏 👩‍💻 📨 🔐. + +✋️ 🚥 👥 ⚙️ 🎏 🏷 ➕1️⃣ *➡ 🛠️*, 👥 💪 📨 👆 👩‍💻 🔐 🔠 👩‍💻. + +!!! danger + 🙅 🏪 ✅ 🔐 👩‍💻 ⚖️ 📨 ⚫️ 📨 💖 👉, 🚥 👆 💭 🌐 ⚠ & 👆 💭 ⚫️❔ 👆 🔨. + +## 🚮 🔢 🏷 + +👥 💪 ↩️ ✍ 🔢 🏷 ⏮️ 🔢 🔐 & 🔢 🏷 🍵 ⚫️: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9 11 16" + {!> ../../../docs_src/response_model/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="9 11 16" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +📥, ✋️ 👆 *➡ 🛠️ 🔢* 🛬 🎏 🔢 👩‍💻 👈 🔌 🔐: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +...👥 📣 `response_model` 👆 🏷 `UserOut`, 👈 🚫 🔌 🔐: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +, **FastAPI** 🔜 ✊ 💅 🖥 👅 🌐 💽 👈 🚫 📣 🔢 🏷 (⚙️ Pydantic). + +### `response_model` ⚖️ 📨 🆎 + +👉 💼, ↩️ 2️⃣ 🏷 🎏, 🚥 👥 ✍ 🔢 📨 🆎 `UserOut`, 👨‍🎨 & 🧰 🔜 😭 👈 👥 🛬 ❌ 🆎, 📚 🎏 🎓. + +👈 ⚫️❔ 👉 🖼 👥 ✔️ 📣 ⚫️ `response_model` 🔢. + +...✋️ 😣 👂 🔛 👀 ❔ ❎ 👈. + +## 📨 🆎 & 💽 🖥 + +➡️ 😣 ⚪️➡️ ⏮️ 🖼. 👥 💚 **✍ 🔢 ⏮️ 1️⃣ 🆎** ✋️ 📨 🕳 👈 🔌 **🌅 💽**. + +👥 💚 FastAPI 🚧 **🖥** 📊 ⚙️ 📨 🏷. + +⏮️ 🖼, ↩️ 🎓 🎏, 👥 ✔️ ⚙️ `response_model` 🔢. ✋️ 👈 ⛓ 👈 👥 🚫 🤚 🐕‍🦺 ⚪️➡️ 👨‍🎨 & 🧰 ✅ 🔢 📨 🆎. + +✋️ 🌅 💼 🌐❔ 👥 💪 🕳 💖 👉, 👥 💚 🏷 **⛽/❎** 📊 👉 🖼. + +& 👈 💼, 👥 💪 ⚙️ 🎓 & 🧬 ✊ 📈 🔢 **🆎 ✍** 🤚 👍 🐕‍🦺 👨‍🎨 & 🧰, & 🤚 FastAPI **💽 🖥**. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9-13 15-16 20" + {!> ../../../docs_src/response_model/tutorial003_01.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7-10 13-14 18" + {!> ../../../docs_src/response_model/tutorial003_01_py310.py!} + ``` + +⏮️ 👉, 👥 🤚 🏭 🐕‍🦺, ⚪️➡️ 👨‍🎨 & ✍ 👉 📟 ☑ ⚖ 🆎, ✋️ 👥 🤚 💽 🖥 ⚪️➡️ FastAPI. + +❔ 🔨 👉 👷 ❓ ➡️ ✅ 👈 👅. 👶 + +### 🆎 ✍ & 🏭 + +🥇 ➡️ 👀 ❔ 👨‍🎨, ✍ & 🎏 🧰 🔜 👀 👉. + +`BaseUser` ✔️ 🧢 🏑. ⤴️ `UserIn` 😖 ⚪️➡️ `BaseUser` & 🚮 `password` 🏑,, ⚫️ 🔜 🔌 🌐 🏑 ⚪️➡️ 👯‍♂️ 🏷. + +👥 ✍ 🔢 📨 🆎 `BaseUser`, ✋️ 👥 🤙 🛬 `UserIn` 👐. + +👨‍🎨, ✍, & 🎏 🧰 🏆 🚫 😭 🔃 👉 ↩️, ⌨ ⚖, `UserIn` 🏿 `BaseUser`, ❔ ⛓ ⚫️ *☑* 🆎 🕐❔ ⚫️❔ ⌛ 🕳 👈 `BaseUser`. + +### FastAPI 💽 🖥 + +🔜, FastAPI, ⚫️ 🔜 👀 📨 🆎 & ⚒ 💭 👈 ⚫️❔ 👆 📨 🔌 **🕴** 🏑 👈 📣 🆎. + +FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 🧬 🚫 ⚙️ 📨 💽 🖥, ⏪ 👆 💪 🔚 🆙 🛬 🌅 🌅 💽 🌘 ⚫️❔ 👆 📈. + +👉 🌌, 👆 💪 🤚 🏆 👯‍♂️ 🌏: 🆎 ✍ ⏮️ **🏭 🐕‍🦺** & **💽 🖥**. + +## 👀 ⚫️ 🩺 + +🕐❔ 👆 👀 🏧 🩺, 👆 💪 ✅ 👈 🔢 🏷 & 🔢 🏷 🔜 👯‍♂️ ✔️ 👫 👍 🎻 🔗: + + + +& 👯‍♂️ 🏷 🔜 ⚙️ 🎓 🛠️ 🧾: + + + +## 🎏 📨 🆎 ✍ + +📤 5️⃣📆 💼 🌐❔ 👆 📨 🕳 👈 🚫 ☑ Pydantic 🏑 & 👆 ✍ ⚫️ 🔢, 🕴 🤚 🐕‍🦺 🚚 🏭 (👨‍🎨, ✍, ♒️). + +### 📨 📨 🔗 + +🏆 ⚠ 💼 🔜 [🛬 📨 🔗 🔬 ⏪ 🏧 🩺](../advanced/response-directly.md){.internal-link target=_blank}. + +```Python hl_lines="8 10-11" +{!> ../../../docs_src/response_model/tutorial003_02.py!} +``` + +👉 🙅 💼 🍵 🔁 FastAPI ↩️ 📨 🆎 ✍ 🎓 (⚖️ 🏿) `Response`. + +& 🧰 🔜 😄 ↩️ 👯‍♂️ `RedirectResponse` & `JSONResponse` 🏿 `Response`, 🆎 ✍ ☑. + +### ✍ 📨 🏿 + +👆 💪 ⚙️ 🏿 `Response` 🆎 ✍: + +```Python hl_lines="8-9" +{!> ../../../docs_src/response_model/tutorial003_03.py!} +``` + +👉 🔜 👷 ↩️ `RedirectResponse` 🏿 `Response`, & FastAPI 🔜 🔁 🍵 👉 🙅 💼. + +### ❌ 📨 🆎 ✍ + +✋️ 🕐❔ 👆 📨 🎏 ❌ 🎚 👈 🚫 ☑ Pydantic 🆎 (✅ 💽 🎚) & 👆 ✍ ⚫️ 💖 👈 🔢, FastAPI 🔜 🔄 ✍ Pydantic 📨 🏷 ⚪️➡️ 👈 🆎 ✍, & 🔜 ❌. + +🎏 🔜 🔨 🚥 👆 ✔️ 🕳 💖 🇪🇺 🖖 🎏 🆎 🌐❔ 1️⃣ ⚖️ 🌅 👫 🚫 ☑ Pydantic 🆎, 🖼 👉 🔜 ❌ 👶: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="10" + {!> ../../../docs_src/response_model/tutorial003_04.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="8" + {!> ../../../docs_src/response_model/tutorial003_04_py310.py!} + ``` + +...👉 ❌ ↩️ 🆎 ✍ 🚫 Pydantic 🆎 & 🚫 👁 `Response` 🎓 ⚖️ 🏿, ⚫️ 🇪🇺 (🙆 2️⃣) 🖖 `Response` & `dict`. + +### ❎ 📨 🏷 + +▶️ ⚪️➡️ 🖼 🔛, 👆 5️⃣📆 🚫 💚 ✔️ 🔢 💽 🔬, 🧾, 🖥, ♒️. 👈 🎭 FastAPI. + +✋️ 👆 💪 💚 🚧 📨 🆎 ✍ 🔢 🤚 🐕‍🦺 ⚪️➡️ 🧰 💖 👨‍🎨 & 🆎 ☑ (✅ ✍). + +👉 💼, 👆 💪 ❎ 📨 🏷 ⚡ ⚒ `response_model=None`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/response_model/tutorial003_05.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/response_model/tutorial003_05_py310.py!} + ``` + +👉 🔜 ⚒ FastAPI 🚶 📨 🏷 ⚡ & 👈 🌌 👆 💪 ✔️ 🙆 📨 🆎 ✍ 👆 💪 🍵 ⚫️ 🤕 👆 FastAPI 🈸. 👶 + +## 📨 🏷 🔢 🔢 + +👆 📨 🏷 💪 ✔️ 🔢 💲, 💖: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="11 13-14" + {!> ../../../docs_src/response_model/tutorial004.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="11 13-14" + {!> ../../../docs_src/response_model/tutorial004_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="9 11-12" + {!> ../../../docs_src/response_model/tutorial004_py310.py!} + ``` + +* `description: Union[str, None] = None` (⚖️ `str | None = None` 🐍 3️⃣.1️⃣0️⃣) ✔️ 🔢 `None`. +* `tax: float = 10.5` ✔️ 🔢 `10.5`. +* `tags: List[str] = []` 🔢 🛁 📇: `[]`. + +✋️ 👆 💪 💚 🚫 👫 ⚪️➡️ 🏁 🚥 👫 🚫 🤙 🏪. + +🖼, 🚥 👆 ✔️ 🏷 ⏮️ 📚 📦 🔢 ☁ 💽, ✋️ 👆 🚫 💚 📨 📶 📏 🎻 📨 🌕 🔢 💲. + +### ⚙️ `response_model_exclude_unset` 🔢 + +👆 💪 ⚒ *➡ 🛠️ 👨‍🎨* 🔢 `response_model_exclude_unset=True`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial004.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial004_py39.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial004_py310.py!} + ``` + +& 👈 🔢 💲 🏆 🚫 🔌 📨, 🕴 💲 🤙 ⚒. + +, 🚥 👆 📨 📨 👈 *➡ 🛠️* 🏬 ⏮️ 🆔 `foo`, 📨 (🚫 ✅ 🔢 💲) 🔜: + +```JSON +{ + "name": "Foo", + "price": 50.2 +} +``` + +!!! info + FastAPI ⚙️ Pydantic 🏷 `.dict()` ⏮️ 🚮 `exclude_unset` 🔢 🏆 👉. + +!!! info + 👆 💪 ⚙️: + + * `response_model_exclude_defaults=True` + * `response_model_exclude_none=True` + + 🔬 Pydantic 🩺 `exclude_defaults` & `exclude_none`. + +#### 📊 ⏮️ 💲 🏑 ⏮️ 🔢 + +✋️ 🚥 👆 📊 ✔️ 💲 🏷 🏑 ⏮️ 🔢 💲, 💖 🏬 ⏮️ 🆔 `bar`: + +```Python hl_lines="3 5" +{ + "name": "Bar", + "description": "The bartenders", + "price": 62, + "tax": 20.2 +} +``` + +👫 🔜 🔌 📨. + +#### 📊 ⏮️ 🎏 💲 🔢 + +🚥 📊 ✔️ 🎏 💲 🔢 🕐, 💖 🏬 ⏮️ 🆔 `baz`: + +```Python hl_lines="3 5-6" +{ + "name": "Baz", + "description": None, + "price": 50.2, + "tax": 10.5, + "tags": [] +} +``` + +FastAPI 🙃 🥃 (🤙, Pydantic 🙃 🥃) 🤔 👈, ✋️ `description`, `tax`, & `tags` ✔️ 🎏 💲 🔢, 👫 ⚒ 🎯 (↩️ ✊ ⚪️➡️ 🔢). + +, 👫 🔜 🔌 🎻 📨. + +!!! tip + 👀 👈 🔢 💲 💪 🕳, 🚫 🕴 `None`. + + 👫 💪 📇 (`[]`), `float` `10.5`, ♒️. + +### `response_model_include` & `response_model_exclude` + +👆 💪 ⚙️ *➡ 🛠️ 👨‍🎨* 🔢 `response_model_include` & `response_model_exclude`. + +👫 ✊ `set` `str` ⏮️ 📛 🔢 🔌 (❎ 🎂) ⚖️ 🚫 (✅ 🎂). + +👉 💪 ⚙️ ⏩ ⌨ 🚥 👆 ✔️ 🕴 1️⃣ Pydantic 🏷 & 💚 ❎ 💽 ⚪️➡️ 🔢. + +!!! tip + ✋️ ⚫️ 👍 ⚙️ 💭 🔛, ⚙️ 💗 🎓, ↩️ 👫 🔢. + + 👉 ↩️ 🎻 🔗 🏗 👆 📱 🗄 (& 🩺) 🔜 1️⃣ 🏁 🏷, 🚥 👆 ⚙️ `response_model_include` ⚖️ `response_model_exclude` 🚫 🔢. + + 👉 ✔ `response_model_by_alias` 👈 👷 ➡. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="31 37" + {!> ../../../docs_src/response_model/tutorial005.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="29 35" + {!> ../../../docs_src/response_model/tutorial005_py310.py!} + ``` + +!!! tip + ❕ `{"name", "description"}` ✍ `set` ⏮️ 📚 2️⃣ 💲. + + ⚫️ 🌓 `set(["name", "description"])`. + +#### ⚙️ `list`Ⓜ ↩️ `set`Ⓜ + +🚥 👆 💭 ⚙️ `set` & ⚙️ `list` ⚖️ `tuple` ↩️, FastAPI 🔜 🗜 ⚫️ `set` & ⚫️ 🔜 👷 ☑: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="31 37" + {!> ../../../docs_src/response_model/tutorial006.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="29 35" + {!> ../../../docs_src/response_model/tutorial006_py310.py!} + ``` + +## 🌃 + +⚙️ *➡ 🛠️ 👨‍🎨* 🔢 `response_model` 🔬 📨 🏷 & ✴️ 🚚 📢 💽 ⛽ 👅. + +⚙️ `response_model_exclude_unset` 📨 🕴 💲 🎯 ⚒. diff --git a/docs/em/docs/tutorial/response-status-code.md b/docs/em/docs/tutorial/response-status-code.md new file mode 100644 index 0000000000..e5149de7db --- /dev/null +++ b/docs/em/docs/tutorial/response-status-code.md @@ -0,0 +1,89 @@ +# 📨 👔 📟 + +🎏 🌌 👆 💪 ✔ 📨 🏷, 👆 💪 📣 🇺🇸🔍 👔 📟 ⚙️ 📨 ⏮️ 🔢 `status_code` 🙆 *➡ 🛠️*: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* ♒️. + +```Python hl_lines="6" +{!../../../docs_src/response_status_code/tutorial001.py!} +``` + +!!! note + 👀 👈 `status_code` 🔢 "👨‍🎨" 👩‍🔬 (`get`, `post`, ♒️). 🚫 👆 *➡ 🛠️ 🔢*, 💖 🌐 🔢 & 💪. + +`status_code` 🔢 📨 🔢 ⏮️ 🇺🇸🔍 👔 📟. + +!!! info + `status_code` 💪 👐 📨 `IntEnum`, ✅ 🐍 `http.HTTPStatus`. + +⚫️ 🔜: + +* 📨 👈 👔 📟 📨. +* 📄 ⚫️ ✅ 🗄 🔗 ( & , 👩‍💻 🔢): + + + +!!! note + 📨 📟 (👀 ⏭ 📄) 🎦 👈 📨 🔨 🚫 ✔️ 💪. + + FastAPI 💭 👉, & 🔜 🏭 🗄 🩺 👈 🇵🇸 📤 🙅‍♂ 📨 💪. + +## 🔃 🇺🇸🔍 👔 📟 + +!!! note + 🚥 👆 ⏪ 💭 ⚫️❔ 🇺🇸🔍 👔 📟, 🚶 ⏭ 📄. + +🇺🇸🔍, 👆 📨 🔢 👔 📟 3️⃣ 9️⃣ 🍕 📨. + +👫 👔 📟 ✔️ 📛 🔗 🤔 👫, ✋️ ⚠ 🍕 🔢. + +📏: + +* `100` & 🔛 "ℹ". 👆 🛎 ⚙️ 👫 🔗. 📨 ⏮️ 👫 👔 📟 🚫🔜 ✔️ 💪. +* **`200`** & 🔛 "🏆" 📨. 👫 🕐 👆 🔜 ⚙️ 🏆. + * `200` 🔢 👔 📟, ❔ ⛓ 🌐 "👌". + * ➕1️⃣ 🖼 🔜 `201`, "✍". ⚫️ 🛎 ⚙️ ⏮️ 🏗 🆕 ⏺ 💽. + * 🎁 💼 `204`, "🙅‍♂ 🎚". 👉 📨 ⚙️ 🕐❔ 📤 🙅‍♂ 🎚 📨 👩‍💻, & 📨 🔜 🚫 ✔️ 💪. +* **`300`** & 🔛 "❎". 📨 ⏮️ 👫 👔 📟 5️⃣📆 ⚖️ 5️⃣📆 🚫 ✔️ 💪, 🌖 `304`, "🚫 🔀", ❔ 🔜 🚫 ✔️ 1️⃣. +* **`400`** & 🔛 "👩‍💻 ❌" 📨. 👫 🥈 🆎 👆 🔜 🎲 ⚙️ 🏆. + * 🖼 `404`, "🚫 🔎" 📨. + * 💊 ❌ ⚪️➡️ 👩‍💻, 👆 💪 ⚙️ `400`. +* `500` & 🔛 💽 ❌. 👆 🌖 🙅 ⚙️ 👫 🔗. 🕐❔ 🕳 🚶 ❌ 🍕 👆 🈸 📟, ⚖️ 💽, ⚫️ 🔜 🔁 📨 1️⃣ 👫 👔 📟. + +!!! tip + 💭 🌅 🔃 🔠 👔 📟 & ❔ 📟 ⚫️❔, ✅ 🏇 🧾 🔃 🇺🇸🔍 👔 📟. + +## ⌨ 💭 📛 + +➡️ 👀 ⏮️ 🖼 🔄: + +```Python hl_lines="6" +{!../../../docs_src/response_status_code/tutorial001.py!} +``` + +`201` 👔 📟 "✍". + +✋️ 👆 🚫 ✔️ ✍ ⚫️❔ 🔠 👉 📟 ⛓. + +👆 💪 ⚙️ 🏪 🔢 ⚪️➡️ `fastapi.status`. + +```Python hl_lines="1 6" +{!../../../docs_src/response_status_code/tutorial002.py!} +``` + +👫 🏪, 👫 🧑‍🤝‍🧑 🎏 🔢, ✋️ 👈 🌌 👆 💪 ⚙️ 👨‍🎨 📋 🔎 👫: + + + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette import status`. + + **FastAPI** 🚚 🎏 `starlette.status` `fastapi.status` 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. + +## 🔀 🔢 + +⏪, [🏧 👩‍💻 🦮](../advanced/response-change-status-code.md){.internal-link target=_blank}, 👆 🔜 👀 ❔ 📨 🎏 👔 📟 🌘 🔢 👆 📣 📥. diff --git a/docs/em/docs/tutorial/schema-extra-example.md b/docs/em/docs/tutorial/schema-extra-example.md new file mode 100644 index 0000000000..d5bf8810aa --- /dev/null +++ b/docs/em/docs/tutorial/schema-extra-example.md @@ -0,0 +1,141 @@ +# 📣 📨 🖼 💽 + +👆 💪 📣 🖼 💽 👆 📱 💪 📨. + +📥 📚 🌌 ⚫️. + +## Pydantic `schema_extra` + +👆 💪 📣 `example` Pydantic 🏷 ⚙️ `Config` & `schema_extra`, 🔬 Pydantic 🩺: 🔗 🛃: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="15-23" + {!> ../../../docs_src/schema_extra_example/tutorial001.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="13-21" + {!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} + ``` + +👈 ➕ ℹ 🔜 🚮-🔢 **🎻 🔗** 👈 🏷, & ⚫️ 🔜 ⚙️ 🛠️ 🩺. + +!!! tip + 👆 💪 ⚙️ 🎏 ⚒ ↔ 🎻 🔗 & 🚮 👆 👍 🛃 ➕ ℹ. + + 🖼 👆 💪 ⚙️ ⚫️ 🚮 🗃 🕸 👩‍💻 🔢, ♒️. + +## `Field` 🌖 ❌ + +🕐❔ ⚙️ `Field()` ⏮️ Pydantic 🏷, 👆 💪 📣 ➕ ℹ **🎻 🔗** 🚶‍♀️ 🙆 🎏 ❌ ❌ 🔢. + +👆 💪 ⚙️ 👉 🚮 `example` 🔠 🏑: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="4 10-13" + {!> ../../../docs_src/schema_extra_example/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="2 8-11" + {!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} + ``` + +!!! warning + 🚧 🤯 👈 📚 ➕ ❌ 🚶‍♀️ 🏆 🚫 🚮 🙆 🔬, 🕴 ➕ ℹ, 🧾 🎯. + +## `example` & `examples` 🗄 + +🕐❔ ⚙️ 🙆: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +👆 💪 📣 💽 `example` ⚖️ 👪 `examples` ⏮️ 🌖 ℹ 👈 🔜 🚮 **🗄**. + +### `Body` ⏮️ `example` + +📥 👥 🚶‍♀️ `example` 📊 ⌛ `Body()`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="20-25" + {!> ../../../docs_src/schema_extra_example/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="18-23" + {!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} + ``` + +### 🖼 🩺 🎚 + +⏮️ 🙆 👩‍🔬 🔛 ⚫️ 🔜 👀 💖 👉 `/docs`: + + + +### `Body` ⏮️ 💗 `examples` + +👐 👁 `example`, 👆 💪 🚶‍♀️ `examples` ⚙️ `dict` ⏮️ **💗 🖼**, 🔠 ⏮️ ➕ ℹ 👈 🔜 🚮 **🗄** 💁‍♂️. + +🔑 `dict` 🔬 🔠 🖼, & 🔠 💲 ➕1️⃣ `dict`. + +🔠 🎯 🖼 `dict` `examples` 💪 🔌: + +* `summary`: 📏 📛 🖼. +* `description`: 📏 📛 👈 💪 🔌 ✍ ✍. +* `value`: 👉 ☑ 🖼 🎦, ✅ `dict`. +* `externalValue`: 🎛 `value`, 📛 ☝ 🖼. 👐 👉 5️⃣📆 🚫 🐕‍🦺 📚 🧰 `value`. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="21-47" + {!> ../../../docs_src/schema_extra_example/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="19-45" + {!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} + ``` + +### 🖼 🩺 🎚 + +⏮️ `examples` 🚮 `Body()` `/docs` 🔜 👀 💖: + + + +## 📡 ℹ + +!!! warning + 👉 📶 📡 ℹ 🔃 🐩 **🎻 🔗** & **🗄**. + + 🚥 💭 🔛 ⏪ 👷 👆, 👈 💪 🥃, & 👆 🎲 🚫 💪 👉 ℹ, 💭 🆓 🚶 👫. + +🕐❔ 👆 🚮 🖼 🔘 Pydantic 🏷, ⚙️ `schema_extra` ⚖️ `Field(example="something")` 👈 🖼 🚮 **🎻 🔗** 👈 Pydantic 🏷. + +& 👈 **🎻 🔗** Pydantic 🏷 🔌 **🗄** 👆 🛠️, & ⤴️ ⚫️ ⚙️ 🩺 🎚. + +**🎻 🔗** 🚫 🤙 ✔️ 🏑 `example` 🐩. ⏮️ ⏬ 🎻 🔗 🔬 🏑 `examples`, ✋️ 🗄 3️⃣.0️⃣.3️⃣ ⚓️ 🔛 🗝 ⏬ 🎻 🔗 👈 🚫 ✔️ `examples`. + +, 🗄 3️⃣.0️⃣.3️⃣ 🔬 🚮 👍 `example` 🔀 ⏬ **🎻 🔗** ⚫️ ⚙️, 🎏 🎯 (✋️ ⚫️ 👁 `example`, 🚫 `examples`), & 👈 ⚫️❔ ⚙️ 🛠️ 🩺 🎚 (⚙️ 🦁 🎚). + +, 👐 `example` 🚫 🍕 🎻 🔗, ⚫️ 🍕 🗄 🛃 ⏬ 🎻 🔗, & 👈 ⚫️❔ 🔜 ⚙️ 🩺 🎚. + +✋️ 🕐❔ 👆 ⚙️ `example` ⚖️ `examples` ⏮️ 🙆 🎏 🚙 (`Query()`, `Body()`, ♒️.) 📚 🖼 🚫 🚮 🎻 🔗 👈 🔬 👈 💽 (🚫 🗄 👍 ⏬ 🎻 🔗), 👫 🚮 🔗 *➡ 🛠️* 📄 🗄 (🏞 🍕 🗄 👈 ⚙️ 🎻 🔗). + +`Path()`, `Query()`, `Header()`, & `Cookie()`, `example` ⚖️ `examples` 🚮 🗄 🔑, `Parameter Object` (🔧). + +& `Body()`, `File()`, & `Form()`, `example` ⚖️ `examples` 📊 🚮 🗄 🔑, `Request Body Object`, 🏑 `content`, 🔛 `Media Type Object` (🔧). + +🔛 🎏 ✋, 📤 🆕 ⏬ 🗄: **3️⃣.1️⃣.0️⃣**, ⏳ 🚀. ⚫️ ⚓️ 🔛 ⏪ 🎻 🔗 & 🏆 🛠️ ⚪️➡️ 🗄 🛃 ⏬ 🎻 🔗 ❎, 💱 ⚒ ⚪️➡️ ⏮️ ⏬ 🎻 🔗, 🌐 👫 🤪 🔺 📉. 👐, 🦁 🎚 ⏳ 🚫 🐕‍🦺 🗄 3️⃣.1️⃣.0️⃣,, 🔜, ⚫️ 👍 😣 ⚙️ 💭 🔛. diff --git a/docs/em/docs/tutorial/security/first-steps.md b/docs/em/docs/tutorial/security/first-steps.md new file mode 100644 index 0000000000..6dec6f2c34 --- /dev/null +++ b/docs/em/docs/tutorial/security/first-steps.md @@ -0,0 +1,182 @@ +# 💂‍♂ - 🥇 🔁 + +➡️ 🌈 👈 👆 ✔️ 👆 **👩‍💻** 🛠️ 🆔. + +& 👆 ✔️ **🕸** ➕1️⃣ 🆔 ⚖️ 🎏 ➡ 🎏 🆔 (⚖️ 📱 🈸). + +& 👆 💚 ✔️ 🌌 🕸 🔓 ⏮️ 👩‍💻, ⚙️ **🆔** & **🔐**. + +👥 💪 ⚙️ **Oauth2️⃣** 🏗 👈 ⏮️ **FastAPI**. + +✋️ ➡️ 🖊 👆 🕰 👂 🌕 📏 🔧 🔎 👈 🐥 🍖 ℹ 👆 💪. + +➡️ ⚙️ 🧰 🚚 **FastAPI** 🍵 💂‍♂. + +## ❔ ⚫️ 👀 + +➡️ 🥇 ⚙️ 📟 & 👀 ❔ ⚫️ 👷, & ⤴️ 👥 🔜 👟 🔙 🤔 ⚫️❔ 😥. + +## ✍ `main.py` + +📁 🖼 📁 `main.py`: + +```Python +{!../../../docs_src/security/tutorial001.py!} +``` + +## 🏃 ⚫️ + +!!! info + 🥇 ❎ `python-multipart`. + + 🤶 Ⓜ. `pip install python-multipart`. + + 👉 ↩️ **Oauth2️⃣** ⚙️ "📨 📊" 📨 `username` & `password`. + +🏃 🖼 ⏮️: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +## ✅ ⚫️ + +🚶 🎓 🩺: http://127.0.0.1:8000/docs. + +👆 🔜 👀 🕳 💖 👉: + + + +!!! check "✔ 🔼 ❗" + 👆 ⏪ ✔️ ✨ 🆕 "✔" 🔼. + + & 👆 *➡ 🛠️* ✔️ 🐥 🔒 🔝-▶️️ ↩ 👈 👆 💪 🖊. + +& 🚥 👆 🖊 ⚫️, 👆 ✔️ 🐥 ✔ 📨 🆎 `username` & `password` (& 🎏 📦 🏑): + + + +!!! note + ⚫️ 🚫 🤔 ⚫️❔ 👆 🆎 📨, ⚫️ 🏆 🚫 👷. ✋️ 👥 🔜 🤚 📤. + +👉 ↗️ 🚫 🕸 🏁 👩‍💻, ✋️ ⚫️ 👑 🏧 🧰 📄 🖥 🌐 👆 🛠️. + +⚫️ 💪 ⚙️ 🕸 🏉 (👈 💪 👆). + +⚫️ 💪 ⚙️ 🥉 🥳 🈸 & ⚙️. + +& ⚫️ 💪 ⚙️ 👆, ℹ, ✅ & 💯 🎏 🈸. + +## `password` 💧 + +🔜 ➡️ 🚶 🔙 👄 & 🤔 ⚫️❔ 🌐 👈. + +`password` "💧" 1️⃣ 🌌 ("💧") 🔬 Oauth2️⃣, 🍵 💂‍♂ & 🤝. + +Oauth2️⃣ 🔧 👈 👩‍💻 ⚖️ 🛠️ 💪 🔬 💽 👈 🔓 👩‍💻. + +✋️ 👉 💼, 🎏 **FastAPI** 🈸 🔜 🍵 🛠️ & 🤝. + +, ➡️ 📄 ⚫️ ⚪️➡️ 👈 📉 ☝ 🎑: + +* 👩‍💻 🆎 `username` & `password` 🕸, & 🎯 `Enter`. +* 🕸 (🏃‍♂ 👩‍💻 🖥) 📨 👈 `username` & `password` 🎯 📛 👆 🛠️ (📣 ⏮️ `tokenUrl="token"`). +* 🛠️ ✅ 👈 `username` & `password`, & 📨 ⏮️ "🤝" (👥 🚫 🛠️ 🙆 👉). + * "🤝" 🎻 ⏮️ 🎚 👈 👥 💪 ⚙️ ⏪ ✔ 👉 👩‍💻. + * 🛎, 🤝 ⚒ 🕛 ⏮️ 🕰. + * , 👩‍💻 🔜 ✔️ 🕹 🔄 ☝ ⏪. + * & 🚥 🤝 📎, ⚠ 🌘. ⚫️ 🚫 💖 🧲 🔑 👈 🔜 👷 ♾ (🏆 💼). +* 🕸 🏪 👈 🤝 🍕 👱. +* 👩‍💻 🖊 🕸 🚶 ➕1️⃣ 📄 🕸 🕸 📱. +* 🕸 💪 ☕ 🌅 💽 ⚪️➡️ 🛠️. + * ✋️ ⚫️ 💪 🤝 👈 🎯 🔗. + * , 🔓 ⏮️ 👆 🛠️, ⚫️ 📨 🎚 `Authorization` ⏮️ 💲 `Bearer ` ➕ 🤝. + * 🚥 🤝 🔌 `foobar`, 🎚 `Authorization` 🎚 🔜: `Bearer foobar`. + +## **FastAPI**'Ⓜ `OAuth2PasswordBearer` + +**FastAPI** 🚚 📚 🧰, 🎏 🎚 ⚛, 🛠️ 👫 💂‍♂ ⚒. + +👉 🖼 👥 🔜 ⚙️ **Oauth2️⃣**, ⏮️ **🔐** 💧, ⚙️ **📨** 🤝. 👥 👈 ⚙️ `OAuth2PasswordBearer` 🎓. + +!!! info + "📨" 🤝 🚫 🕴 🎛. + + ✋️ ⚫️ 🏆 1️⃣ 👆 ⚙️ 💼. + + & ⚫️ 💪 🏆 🏆 ⚙️ 💼, 🚥 👆 Oauth2️⃣ 🕴 & 💭 ⚫️❔ ⚫️❔ 📤 ➕1️⃣ 🎛 👈 ♣ 👻 👆 💪. + + 👈 💼, **FastAPI** 🚚 👆 ⏮️ 🧰 🏗 ⚫️. + +🕐❔ 👥 ✍ 👐 `OAuth2PasswordBearer` 🎓 👥 🚶‍♀️ `tokenUrl` 🔢. 👉 🔢 🔌 📛 👈 👩‍💻 (🕸 🏃 👩‍💻 🖥) 🔜 ⚙️ 📨 `username` & `password` ✔ 🤚 🤝. + +```Python hl_lines="6" +{!../../../docs_src/security/tutorial001.py!} +``` + +!!! tip + 📥 `tokenUrl="token"` 🔗 ⚖ 📛 `token` 👈 👥 🚫 ✍. ⚫️ ⚖ 📛, ⚫️ 🌓 `./token`. + + ↩️ 👥 ⚙️ ⚖ 📛, 🚥 👆 🛠️ 🔎 `https://example.com/`, ⤴️ ⚫️ 🔜 🔗 `https://example.com/token`. ✋️ 🚥 👆 🛠️ 🔎 `https://example.com/api/v1/`, ⤴️ ⚫️ 🔜 🔗 `https://example.com/api/v1/token`. + + ⚙️ ⚖ 📛 ⚠ ⚒ 💭 👆 🈸 🚧 👷 🏧 ⚙️ 💼 💖 [⛅ 🗳](../../advanced/behind-a-proxy.md){.internal-link target=_blank}. + +👉 🔢 🚫 ✍ 👈 🔗 / *➡ 🛠️*, ✋️ 📣 👈 📛 `/token` 🔜 1️⃣ 👈 👩‍💻 🔜 ⚙️ 🤚 🤝. 👈 ℹ ⚙️ 🗄, & ⤴️ 🎓 🛠️ 🧾 ⚙️. + +👥 🔜 🔜 ✍ ☑ ➡ 🛠️. + +!!! info + 🚥 👆 📶 ⚠ "✍" 👆 💪 👎 👗 🔢 📛 `tokenUrl` ↩️ `token_url`. + + 👈 ↩️ ⚫️ ⚙️ 🎏 📛 🗄 🔌. 👈 🚥 👆 💪 🔬 🌅 🔃 🙆 👫 💂‍♂ ⚖ 👆 💪 📁 & 📋 ⚫️ 🔎 🌖 ℹ 🔃 ⚫️. + +`oauth2_scheme` 🔢 👐 `OAuth2PasswordBearer`, ✋️ ⚫️ "🇧🇲". + +⚫️ 💪 🤙: + +```Python +oauth2_scheme(some, parameters) +``` + +, ⚫️ 💪 ⚙️ ⏮️ `Depends`. + +### ⚙️ ⚫️ + +🔜 👆 💪 🚶‍♀️ 👈 `oauth2_scheme` 🔗 ⏮️ `Depends`. + +```Python hl_lines="10" +{!../../../docs_src/security/tutorial001.py!} +``` + +👉 🔗 🔜 🚚 `str` 👈 🛠️ 🔢 `token` *➡ 🛠️ 🔢*. + +**FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 👉 🔗 🔬 "💂‍♂ ⚖" 🗄 🔗 (& 🏧 🛠️ 🩺). + +!!! info "📡 ℹ" + **FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 🎓 `OAuth2PasswordBearer` (📣 🔗) 🔬 💂‍♂ ⚖ 🗄 ↩️ ⚫️ 😖 ⚪️➡️ `fastapi.security.oauth2.OAuth2`, ❔ 🔄 😖 ⚪️➡️ `fastapi.security.base.SecurityBase`. + + 🌐 💂‍♂ 🚙 👈 🛠️ ⏮️ 🗄 (& 🏧 🛠️ 🩺) 😖 ⚪️➡️ `SecurityBase`, 👈 ❔ **FastAPI** 💪 💭 ❔ 🛠️ 👫 🗄. + +## ⚫️❔ ⚫️ 🔨 + +⚫️ 🔜 🚶 & 👀 📨 👈 `Authorization` 🎚, ✅ 🚥 💲 `Bearer ` ➕ 🤝, & 🔜 📨 🤝 `str`. + +🚥 ⚫️ 🚫 👀 `Authorization` 🎚, ⚖️ 💲 🚫 ✔️ `Bearer ` 🤝, ⚫️ 🔜 📨 ⏮️ 4️⃣0️⃣1️⃣ 👔 📟 ❌ (`UNAUTHORIZED`) 🔗. + +👆 🚫 ✔️ ✅ 🚥 🤝 🔀 📨 ❌. 👆 💪 💭 👈 🚥 👆 🔢 🛠️, ⚫️ 🔜 ✔️ `str` 👈 🤝. + +👆 💪 🔄 ⚫️ ⏪ 🎓 🩺: + + + +👥 🚫 ✔ 🔬 🤝, ✋️ 👈 ▶️ ⏪. + +## 🌃 + +, 3️⃣ ⚖️ 4️⃣ ➕ ⏸, 👆 ⏪ ✔️ 🐒 📨 💂‍♂. diff --git a/docs/em/docs/tutorial/security/get-current-user.md b/docs/em/docs/tutorial/security/get-current-user.md new file mode 100644 index 0000000000..455cb4f46f --- /dev/null +++ b/docs/em/docs/tutorial/security/get-current-user.md @@ -0,0 +1,151 @@ +# 🤚 ⏮️ 👩‍💻 + +⏮️ 📃 💂‍♂ ⚙️ (❔ 🧢 🔛 🔗 💉 ⚙️) 🤝 *➡ 🛠️ 🔢* `token` `str`: + +```Python hl_lines="10" +{!../../../docs_src/security/tutorial001.py!} +``` + +✋️ 👈 🚫 👈 ⚠. + +➡️ ⚒ ⚫️ 🤝 👥 ⏮️ 👩‍💻. + +## ✍ 👩‍💻 🏷 + +🥇, ➡️ ✍ Pydantic 👩‍💻 🏷. + +🎏 🌌 👥 ⚙️ Pydantic 📣 💪, 👥 💪 ⚙️ ⚫️ 🙆 🙆: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="5 12-16" + {!> ../../../docs_src/security/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="3 10-14" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +## ✍ `get_current_user` 🔗 + +➡️ ✍ 🔗 `get_current_user`. + +💭 👈 🔗 💪 ✔️ 🎧-🔗 ❓ + +`get_current_user` 🔜 ✔️ 🔗 ⏮️ 🎏 `oauth2_scheme` 👥 ✍ ⏭. + +🎏 👥 🔨 ⏭ *➡ 🛠️* 🔗, 👆 🆕 🔗 `get_current_user` 🔜 📨 `token` `str` ⚪️➡️ 🎧-🔗 `oauth2_scheme`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="25" + {!> ../../../docs_src/security/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="23" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +## 🤚 👩‍💻 + +`get_current_user` 🔜 ⚙️ (❌) 🚙 🔢 👥 ✍, 👈 ✊ 🤝 `str` & 📨 👆 Pydantic `User` 🏷: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="19-22 26-27" + {!> ../../../docs_src/security/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="17-20 24-25" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +## 💉 ⏮️ 👩‍💻 + +🔜 👥 💪 ⚙️ 🎏 `Depends` ⏮️ 👆 `get_current_user` *➡ 🛠️*: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="31" + {!> ../../../docs_src/security/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="29" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +👀 👈 👥 📣 🆎 `current_user` Pydantic 🏷 `User`. + +👉 🔜 ℹ 🇺🇲 🔘 🔢 ⏮️ 🌐 🛠️ & 🆎 ✅. + +!!! tip + 👆 5️⃣📆 💭 👈 📨 💪 📣 ⏮️ Pydantic 🏷. + + 📥 **FastAPI** 🏆 🚫 🤚 😨 ↩️ 👆 ⚙️ `Depends`. + +!!! check + 🌌 👉 🔗 ⚙️ 🏗 ✔ 👥 ✔️ 🎏 🔗 (🎏 "☑") 👈 🌐 📨 `User` 🏷. + + 👥 🚫 🚫 ✔️ 🕴 1️⃣ 🔗 👈 💪 📨 👈 🆎 💽. + +## 🎏 🏷 + +👆 💪 🔜 🤚 ⏮️ 👩‍💻 🔗 *➡ 🛠️ 🔢* & 🙅 ⏮️ 💂‍♂ 🛠️ **🔗 💉** 🎚, ⚙️ `Depends`. + +& 👆 💪 ⚙️ 🙆 🏷 ⚖️ 💽 💂‍♂ 📄 (👉 💼, Pydantic 🏷 `User`). + +✋️ 👆 🚫 🚫 ⚙️ 🎯 💽 🏷, 🎓 ⚖️ 🆎. + +👆 💚 ✔️ `id` & `email` & 🚫 ✔️ 🙆 `username` 👆 🏷 ❓ 💭. 👆 💪 ⚙️ 👉 🎏 🧰. + +👆 💚 ✔️ `str`❓ ⚖️ `dict`❓ ⚖️ 💽 🎓 🏷 👐 🔗 ❓ ⚫️ 🌐 👷 🎏 🌌. + +👆 🤙 🚫 ✔️ 👩‍💻 👈 🕹 👆 🈸 ✋️ 🤖, 🤖, ⚖️ 🎏 ⚙️, 👈 ✔️ 🔐 🤝 ❓ 🔄, ⚫️ 🌐 👷 🎏. + +⚙️ 🙆 😇 🏷, 🙆 😇 🎓, 🙆 😇 💽 👈 👆 💪 👆 🈸. **FastAPI** ✔️ 👆 📔 ⏮️ 🔗 💉 ⚙️. + +## 📟 📐 + +👉 🖼 5️⃣📆 😑 🔁. ✔️ 🤯 👈 👥 🌀 💂‍♂, 📊 🏷, 🚙 🔢 & *➡ 🛠️* 🎏 📁. + +✋️ 📥 🔑 ☝. + +💂‍♂ & 🔗 💉 💩 ✍ 🕐. + +& 👆 💪 ⚒ ⚫️ 🏗 👆 💚. & , ✔️ ⚫️ ✍ 🕴 🕐, 👁 🥉. ⏮️ 🌐 💪. + +✋️ 👆 💪 ✔️ 💯 🔗 (*➡ 🛠️*) ⚙️ 🎏 💂‍♂ ⚙️. + +& 🌐 👫 (⚖️ 🙆 ↔ 👫 👈 👆 💚) 💪 ✊ 📈 🏤-⚙️ 👫 🔗 ⚖️ 🙆 🎏 🔗 👆 ✍. + +& 🌐 👉 💯 *➡ 🛠️* 💪 🤪 3️⃣ ⏸: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="30-32" + {!> ../../../docs_src/security/tutorial002.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="28-30" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +## 🌃 + +👆 💪 🔜 🤚 ⏮️ 👩‍💻 🔗 👆 *➡ 🛠️ 🔢*. + +👥 ⏪ 😬 📤. + +👥 💪 🚮 *➡ 🛠️* 👩‍💻/👩‍💻 🤙 📨 `username` & `password`. + +👈 👟 ⏭. diff --git a/docs/em/docs/tutorial/security/index.md b/docs/em/docs/tutorial/security/index.md new file mode 100644 index 0000000000..d76f7203fe --- /dev/null +++ b/docs/em/docs/tutorial/security/index.md @@ -0,0 +1,101 @@ +# 💂‍♂ + +📤 📚 🌌 🍵 💂‍♂, 🤝 & ✔. + +& ⚫️ 🛎 🏗 & "⚠" ❔. + +📚 🛠️ & ⚙️ 🍵 💂‍♂ & 🤝 ✊ 🦏 💸 🎯 & 📟 (📚 💼 ⚫️ 💪 5️⃣0️⃣ 💯 ⚖️ 🌅 🌐 📟 ✍). + +**FastAPI** 🚚 📚 🧰 ℹ 👆 🙅 ⏮️ **💂‍♂** 💪, 📉, 🐩 🌌, 🍵 ✔️ 🔬 & 💡 🌐 💂‍♂ 🔧. + +✋️ 🥇, ➡️ ✅ 🤪 🔧. + +## 🏃 ❓ + +🚥 👆 🚫 💅 🔃 🙆 👉 ⚖ & 👆 💪 🚮 💂‍♂ ⏮️ 🤝 ⚓️ 🔛 🆔 & 🔐 *▶️️ 🔜*, 🚶 ⏭ 📃. + +## Oauth2️⃣ + +Oauth2️⃣ 🔧 👈 🔬 📚 🌌 🍵 🤝 & ✔. + +⚫️ 🔬 🔧 & 📔 📚 🏗 ⚙️ 💼. + +⚫️ 🔌 🌌 🔓 ⚙️ "🥉 🥳". + +👈 ⚫️❔ 🌐 ⚙️ ⏮️ "💳 ⏮️ 👱📔, 🇺🇸🔍, 👱📔, 📂" ⚙️ 🔘. + +### ✳ 1️⃣ + +📤 ✳ 1️⃣, ❔ 📶 🎏 ⚪️➡️ Oauth2️⃣, & 🌖 🏗, ⚫️ 🔌 🔗 🔧 🔛 ❔ 🗜 📻. + +⚫️ 🚫 📶 🌟 ⚖️ ⚙️ 🛎. + +Oauth2️⃣ 🚫 ✔ ❔ 🗜 📻, ⚫️ ⌛ 👆 ✔️ 👆 🈸 🍦 ⏮️ 🇺🇸🔍. + +!!! tip + 📄 🔃 **🛠️** 👆 🔜 👀 ❔ ⚒ 🆙 🇺🇸🔍 🆓, ⚙️ Traefik & ➡️ 🗜. + + +## 👩‍💻 🔗 + +👩‍💻 🔗 ➕1️⃣ 🔧, 🧢 🔛 **Oauth2️⃣**. + +⚫️ ↔ Oauth2️⃣ ✔ 👜 👈 📶 🌌 Oauth2️⃣, 🔄 ⚒ ⚫️ 🌅 🛠️. + +🖼, 🇺🇸🔍 💳 ⚙️ 👩‍💻 🔗 (❔ 🔘 ⚙️ Oauth2️⃣). + +✋️ 👱📔 💳 🚫 🐕‍🦺 👩‍💻 🔗. ⚫️ ✔️ 🚮 👍 🍛 Oauth2️⃣. + +### 👩‍💻 (🚫 "👩‍💻 🔗") + +📤 "👩‍💻" 🔧. 👈 🔄 ❎ 🎏 👜 **👩‍💻 🔗**, ✋️ 🚫 ⚓️ 🔛 Oauth2️⃣. + +, ⚫️ 🏁 🌖 ⚙️. + +⚫️ 🚫 📶 🌟 ⚖️ ⚙️ 🛎. + +## 🗄 + +🗄 (⏪ 💭 🦁) 📂 🔧 🏗 🔗 (🔜 🍕 💾 🏛). + +**FastAPI** ⚓️ 🔛 **🗄**. + +👈 ⚫️❔ ⚒ ⚫️ 💪 ✔️ 💗 🏧 🎓 🧾 🔢, 📟 ⚡, ♒️. + +🗄 ✔️ 🌌 🔬 💗 💂‍♂ "⚖". + +⚙️ 👫, 👆 💪 ✊ 📈 🌐 👫 🐩-⚓️ 🧰, 🔌 👉 🎓 🧾 ⚙️. + +🗄 🔬 📄 💂‍♂ ⚖: + +* `apiKey`: 🈸 🎯 🔑 👈 💪 👟 ⚪️➡️: + * 🔢 🔢. + * 🎚. + * 🍪. +* `http`: 🐩 🇺🇸🔍 🤝 ⚙️, 🔌: + * `bearer`: 🎚 `Authorization` ⏮️ 💲 `Bearer ` ➕ 🤝. 👉 😖 ⚪️➡️ Oauth2️⃣. + * 🇺🇸🔍 🔰 🤝. + * 🇺🇸🔍 📰, ♒️. +* `oauth2`: 🌐 Oauth2️⃣ 🌌 🍵 💂‍♂ (🤙 "💧"). + * 📚 👫 💧 ☑ 🏗 ✳ 2️⃣.0️⃣ 🤝 🐕‍🦺 (💖 🇺🇸🔍, 👱📔, 👱📔, 📂, ♒️): + * `implicit` + * `clientCredentials` + * `authorizationCode` + * ✋️ 📤 1️⃣ 🎯 "💧" 👈 💪 👌 ⚙️ 🚚 🤝 🎏 🈸 🔗: + * `password`: ⏭ 📃 🔜 📔 🖼 👉. +* `openIdConnect`: ✔️ 🌌 🔬 ❔ 🔎 Oauth2️⃣ 🤝 📊 🔁. + * 👉 🏧 🔍 ⚫️❔ 🔬 👩‍💻 🔗 🔧. + + +!!! tip + 🛠️ 🎏 🤝/✔ 🐕‍🦺 💖 🇺🇸🔍, 👱📔, 👱📔, 📂, ♒️. 💪 & 📶 ⏩. + + 🌅 🏗 ⚠ 🏗 🤝/✔ 🐕‍🦺 💖 👈, ✋️ **FastAPI** 🤝 👆 🧰 ⚫️ 💪, ⏪ 🔨 🏋️ 🏋‍♂ 👆. + +## **FastAPI** 🚙 + +FastAPI 🚚 📚 🧰 🔠 👉 💂‍♂ ⚖ `fastapi.security` 🕹 👈 📉 ⚙️ 👉 💂‍♂ 🛠️. + +⏭ 📃 👆 🔜 👀 ❔ 🚮 💂‍♂ 👆 🛠️ ⚙️ 📚 🧰 🚚 **FastAPI**. + +& 👆 🔜 👀 ❔ ⚫️ 🤚 🔁 🛠️ 🔘 🎓 🧾 ⚙️. diff --git a/docs/em/docs/tutorial/security/oauth2-jwt.md b/docs/em/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 0000000000..bc207c5666 --- /dev/null +++ b/docs/em/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,297 @@ +# Oauth2️⃣ ⏮️ 🔐 (& 🔁), 📨 ⏮️ 🥙 🤝 + +🔜 👈 👥 ✔️ 🌐 💂‍♂ 💧, ➡️ ⚒ 🈸 🤙 🔐, ⚙️ 🥙 🤝 & 🔐 🔐 🔁. + +👉 📟 🕳 👆 💪 🤙 ⚙️ 👆 🈸, 🖊 🔐 #️⃣ 👆 💽, ♒️. + +👥 🔜 ▶️ ⚪️➡️ 🌐❔ 👥 ◀️ ⏮️ 📃 & 📈 ⚫️. + +## 🔃 🥙 + +🥙 ⛓ "🎻 🕸 🤝". + +⚫️ 🐩 🚫 🎻 🎚 📏 💧 🎻 🍵 🚀. ⚫️ 👀 💖 👉: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +⚫️ 🚫 🗜,, 🙆 💪 🛡 ℹ ⚪️➡️ 🎚. + +✋️ ⚫️ 🛑. , 🕐❔ 👆 📨 🤝 👈 👆 ♨, 👆 💪 ✔ 👈 👆 🤙 ♨ ⚫️. + +👈 🌌, 👆 💪 ✍ 🤝 ⏮️ 👔, ➡️ 💬, 1️⃣ 🗓️. & ⤴️ 🕐❔ 👩‍💻 👟 🔙 ⏭ 📆 ⏮️ 🤝, 👆 💭 👈 👩‍💻 🕹 👆 ⚙️. + +⏮️ 🗓️, 🤝 🔜 🕛 & 👩‍💻 🔜 🚫 ✔ & 🔜 ✔️ 🛑 🔄 🤚 🆕 🤝. & 🚥 👩‍💻 (⚖️ 🥉 🥳) 🔄 🔀 🤝 🔀 👔, 👆 🔜 💪 🔎 ⚫️, ↩️ 💳 🔜 🚫 🏏. + +🚥 👆 💚 🤾 ⏮️ 🥙 🤝 & 👀 ❔ 👫 👷, ✅ https://jwt.io. + +## ❎ `python-jose` + +👥 💪 ❎ `python-jose` 🏗 & ✔ 🥙 🤝 🐍: + +
+ +```console +$ pip install "python-jose[cryptography]" + +---> 100% +``` + +
+ +🐍-🇩🇬 🚚 🔐 👩‍💻 ➕. + +📥 👥 ⚙️ 👍 1️⃣: )/⚛. + +!!! tip + 👉 🔰 ⏪ ⚙️ PyJWT. + + ✋️ ⚫️ ℹ ⚙️ 🐍-🇩🇬 ↩️ ⚫️ 🚚 🌐 ⚒ ⚪️➡️ PyJWT ➕ ➕ 👈 👆 💪 💪 ⏪ 🕐❔ 🏗 🛠️ ⏮️ 🎏 🧰. + +## 🔐 🔁 + +"🔁" ⛓ 🏭 🎚 (🔐 👉 💼) 🔘 🔁 🔢 (🎻) 👈 👀 💖 🙃. + +🕐❔ 👆 🚶‍♀️ ⚫️❔ 🎏 🎚 (⚫️❔ 🎏 🔐) 👆 🤚 ⚫️❔ 🎏 🙃. + +✋️ 👆 🚫🔜 🗜 ⚪️➡️ 🙃 🔙 🔐. + +### ⚫️❔ ⚙️ 🔐 🔁 + +🚥 👆 💽 📎, 🧙‍♀ 🏆 🚫 ✔️ 👆 👩‍💻' 🔢 🔐, 🕴#️⃣. + +, 🧙‍♀ 🏆 🚫 💪 🔄 ⚙️ 👈 🔐 ➕1️⃣ ⚙️ (📚 👩‍💻 ⚙️ 🎏 🔐 🌐, 👉 🔜 ⚠). + +## ❎ `passlib` + +🇸🇲 👑 🐍 📦 🍵 🔐#️⃣. + +⚫️ 🐕‍🦺 📚 🔐 🔁 📊 & 🚙 👷 ⏮️ 👫. + +👍 📊 "🐡". + +, ❎ 🇸🇲 ⏮️ 🐡: + +
+ +```console +$ pip install "passlib[bcrypt]" + +---> 100% +``` + +
+ +!!! tip + ⏮️ `passlib`, 👆 💪 🔗 ⚫️ 💪 ✍ 🔐 ✍ **✳**, **🏺** 💂‍♂ 🔌-⚖️ 📚 🎏. + + , 👆 🔜 💪, 🖼, 💰 🎏 📊 ⚪️➡️ ✳ 🈸 💽 ⏮️ FastAPI 🈸. ⚖️ 📉 ↔ ✳ 🈸 ⚙️ 🎏 💽. + + & 👆 👩‍💻 🔜 💪 💳 ⚪️➡️ 👆 ✳ 📱 ⚖️ ⚪️➡️ 👆 **FastAPI** 📱, 🎏 🕰. + +## #️⃣ & ✔ 🔐 + +🗄 🧰 👥 💪 ⚪️➡️ `passlib`. + +✍ 🇸🇲 "🔑". 👉 ⚫️❔ 🔜 ⚙️ #️⃣ & ✔ 🔐. + +!!! tip + 🇸🇲 🔑 ✔️ 🛠️ ⚙️ 🎏 🔁 📊, 🔌 😢 🗝 🕐 🕴 ✔ ✔ 👫, ♒️. + + 🖼, 👆 💪 ⚙️ ⚫️ ✍ & ✔ 🔐 🏗 ➕1️⃣ ⚙️ (💖 ✳) ✋️ #️⃣ 🙆 🆕 🔐 ⏮️ 🎏 📊 💖 🐡. + + & 🔗 ⏮️ 🌐 👫 🎏 🕰. + +✍ 🚙 🔢 #️⃣ 🔐 👟 ⚪️➡️ 👩‍💻. + +& ➕1️⃣ 🚙 ✔ 🚥 📨 🔐 🏏 #️⃣ 🏪. + +& ➕1️⃣ 1️⃣ 🔓 & 📨 👩‍💻. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="7 48 55-56 59-60 69-75" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="6 47 54-55 58-59 68-74" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` + +!!! note + 🚥 👆 ✅ 🆕 (❌) 💽 `fake_users_db`, 👆 🔜 👀 ❔ #️⃣ 🔐 👀 💖 🔜: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. + +## 🍵 🥙 🤝 + +🗄 🕹 ❎. + +✍ 🎲 ㊙ 🔑 👈 🔜 ⚙️ 🛑 🥙 🤝. + +🏗 🔐 🎲 ㊙ 🔑 ⚙️ 📋: + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +& 📁 🔢 🔢 `SECRET_KEY` (🚫 ⚙️ 1️⃣ 🖼). + +✍ 🔢 `ALGORITHM` ⏮️ 📊 ⚙️ 🛑 🥙 🤝 & ⚒ ⚫️ `"HS256"`. + +✍ 🔢 👔 🤝. + +🔬 Pydantic 🏷 👈 🔜 ⚙️ 🤝 🔗 📨. + +✍ 🚙 🔢 🏗 🆕 🔐 🤝. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="6 12-14 28-30 78-86" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="5 11-13 27-29 77-85" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` + +## ℹ 🔗 + +ℹ `get_current_user` 📨 🎏 🤝 ⏭, ✋️ 👉 🕰, ⚙️ 🥙 🤝. + +🔣 📨 🤝, ✔ ⚫️, & 📨 ⏮️ 👩‍💻. + +🚥 🤝 ❌, 📨 🇺🇸🔍 ❌ ▶️️ ↖️. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="89-106" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="88-105" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` + +## ℹ `/token` *➡ 🛠️* + +✍ `timedelta` ⏮️ 👔 🕰 🤝. + +✍ 🎰 🥙 🔐 🤝 & 📨 ⚫️. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="115-128" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="114-127" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` + +### 📡 ℹ 🔃 🥙 "📄" `sub` + +🥙 🔧 💬 👈 📤 🔑 `sub`, ⏮️ 📄 🤝. + +⚫️ 📦 ⚙️ ⚫️, ✋️ 👈 🌐❔ 👆 🔜 🚮 👩‍💻 🆔, 👥 ⚙️ ⚫️ 📥. + +🥙 5️⃣📆 ⚙️ 🎏 👜 ↖️ ⚪️➡️ ⚖ 👩‍💻 & 🤝 👫 🎭 🛠️ 🔗 🔛 👆 🛠️. + +🖼, 👆 💪 🔬 "🚘" ⚖️ "📰 🏤". + +⤴️ 👆 💪 🚮 ✔ 🔃 👈 👨‍💼, 💖 "💾" (🚘) ⚖️ "✍" (📰). + +& ⤴️, 👆 💪 🤝 👈 🥙 🤝 👩‍💻 (⚖️ 🤖), & 👫 💪 ⚙️ ⚫️ 🎭 👈 🎯 (💾 🚘, ⚖️ ✍ 📰 🏤) 🍵 💆‍♂ ✔️ 🏧, ⏮️ 🥙 🤝 👆 🛠️ 🏗 👈. + +⚙️ 👫 💭, 🥙 💪 ⚙️ 🌌 🌖 🤓 😐. + +📚 💼, 📚 👈 👨‍💼 💪 ✔️ 🎏 🆔, ➡️ 💬 `foo` (👩‍💻 `foo`, 🚘 `foo`, & 📰 🏤 `foo`). + +, ❎ 🆔 💥, 🕐❔ 🏗 🥙 🤝 👩‍💻, 👆 💪 🔡 💲 `sub` 🔑, ✅ ⏮️ `username:`. , 👉 🖼, 💲 `sub` 💪 ✔️: `username:johndoe`. + +⚠ 👜 ✔️ 🤯 👈 `sub` 🔑 🔜 ✔️ 😍 🆔 🤭 🎂 🈸, & ⚫️ 🔜 🎻. + +## ✅ ⚫️ + +🏃 💽 & 🚶 🩺: http://127.0.0.1:8000/docs. + +👆 🔜 👀 👩‍💻 🔢 💖: + + + +✔ 🈸 🎏 🌌 ⏭. + +⚙️ 🎓: + +🆔: `johndoe` +🔐: `secret` + +!!! check + 👀 👈 🕳 📟 🔢 🔐 "`secret`", 👥 🕴 ✔️ #️⃣ ⏬. + + + +🤙 🔗 `/users/me/`, 👆 🔜 🤚 📨: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +🚥 👆 📂 👩‍💻 🧰, 👆 💪 👀 ❔ 📊 📨 🕴 🔌 🤝, 🔐 🕴 📨 🥇 📨 🔓 👩‍💻 & 🤚 👈 🔐 🤝, ✋️ 🚫 ⏮️: + + + +!!! note + 👀 🎚 `Authorization`, ⏮️ 💲 👈 ▶️ ⏮️ `Bearer `. + +## 🏧 ⚙️ ⏮️ `scopes` + +Oauth2️⃣ ✔️ 🔑 "↔". + +👆 💪 ⚙️ 👫 🚮 🎯 ⚒ ✔ 🥙 🤝. + +⤴️ 👆 💪 🤝 👉 🤝 👩‍💻 🔗 ⚖️ 🥉 🥳, 🔗 ⏮️ 👆 🛠️ ⏮️ ⚒ 🚫. + +👆 💪 💡 ❔ ⚙️ 👫 & ❔ 👫 🛠️ 🔘 **FastAPI** ⏪ **🏧 👩‍💻 🦮**. + +## 🌃 + +⏮️ ⚫️❔ 👆 ✔️ 👀 🆙 🔜, 👆 💪 ⚒ 🆙 🔐 **FastAPI** 🈸 ⚙️ 🐩 💖 Oauth2️⃣ & 🥙. + +🌖 🙆 🛠️ 🚚 💂‍♂ ▶️️ 👍 🏗 📄 🔜. + +📚 📦 👈 📉 ⚫️ 📚 ✔️ ⚒ 📚 ⚠ ⏮️ 💽 🏷, 💽, & 💪 ⚒. & 👉 📦 👈 📉 👜 💁‍♂️ 🌅 🤙 ✔️ 💂‍♂ ⚠ 🔘. + +--- + +**FastAPI** 🚫 ⚒ 🙆 ⚠ ⏮️ 🙆 💽, 💽 🏷 ⚖️ 🧰. + +⚫️ 🤝 👆 🌐 💪 ⚒ 🕐 👈 👖 👆 🏗 🏆. + +& 👆 💪 ⚙️ 🔗 📚 👍 🚧 & 🛎 ⚙️ 📦 💖 `passlib` & `python-jose`, ↩️ **FastAPI** 🚫 🚚 🙆 🏗 🛠️ 🛠️ 🔢 📦. + +✋️ ⚫️ 🚚 👆 🧰 📉 🛠️ 🌅 💪 🍵 🎯 💪, ⚖, ⚖️ 💂‍♂. + +& 👆 💪 ⚙️ & 🛠️ 🔐, 🐩 🛠️, 💖 Oauth2️⃣ 📶 🙅 🌌. + +👆 💪 💡 🌅 **🏧 👩‍💻 🦮** 🔃 ❔ ⚙️ Oauth2️⃣ "↔", 🌖 👌-🧽 ✔ ⚙️, 📄 👫 🎏 🐩. Oauth2️⃣ ⏮️ ↔ 🛠️ ⚙️ 📚 🦏 🤝 🐕‍🦺, 💖 👱📔, 🇺🇸🔍, 📂, 🤸‍♂, 👱📔, ♒️. ✔ 🥉 🥳 🈸 🔗 ⏮️ 👫 🔗 🔛 👨‍💼 👫 👩‍💻. diff --git a/docs/em/docs/tutorial/security/simple-oauth2.md b/docs/em/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 0000000000..765d940394 --- /dev/null +++ b/docs/em/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,315 @@ +# 🙅 Oauth2️⃣ ⏮️ 🔐 & 📨 + +🔜 ➡️ 🏗 ⚪️➡️ ⏮️ 📃 & 🚮 ❌ 🍕 ✔️ 🏁 💂‍♂ 💧. + +## 🤚 `username` & `password` + +👥 🔜 ⚙️ **FastAPI** 💂‍♂ 🚙 🤚 `username` & `password`. + +Oauth2️⃣ ✔ 👈 🕐❔ ⚙️ "🔐 💧" (👈 👥 ⚙️) 👩‍💻/👩‍💻 🔜 📨 `username` & `password` 🏑 📨 💽. + +& 🔌 💬 👈 🏑 ✔️ 🌟 💖 👈. `user-name` ⚖️ `email` 🚫🔜 👷. + +✋️ 🚫 😟, 👆 💪 🎦 ⚫️ 👆 🎋 👆 🏁 👩‍💻 🕸. + +& 👆 💽 🏷 💪 ⚙️ 🙆 🎏 📛 👆 💚. + +✋️ 💳 *➡ 🛠️*, 👥 💪 ⚙️ 👉 📛 🔗 ⏮️ 🔌 (& 💪, 🖼, ⚙️ 🛠️ 🛠️ 🧾 ⚙️). + +🔌 🇵🇸 👈 `username` & `password` 🔜 📨 📨 💽 (, 🙅‍♂ 🎻 📥). + +### `scope` + +🔌 💬 👈 👩‍💻 💪 📨 ➕1️⃣ 📨 🏑 "`scope`". + +📨 🏑 📛 `scope` (⭐), ✋️ ⚫️ 🤙 📏 🎻 ⏮️ "↔" 🎏 🚀. + +🔠 "↔" 🎻 (🍵 🚀). + +👫 🛎 ⚙️ 📣 🎯 💂‍♂ ✔, 🖼: + +* `users:read` ⚖️ `users:write` ⚠ 🖼. +* `instagram_basic` ⚙️ 👱📔 / 👱📔. +* `https://www.googleapis.com/auth/drive` ⚙️ 🇺🇸🔍. + +!!! info + Oauth2️⃣ "↔" 🎻 👈 📣 🎯 ✔ ✔. + + ⚫️ 🚫 🤔 🚥 ⚫️ ✔️ 🎏 🦹 💖 `:` ⚖️ 🚥 ⚫️ 📛. + + 👈 ℹ 🛠️ 🎯. + + Oauth2️⃣ 👫 🎻. + +## 📟 🤚 `username` & `password` + +🔜 ➡️ ⚙️ 🚙 🚚 **FastAPI** 🍵 👉. + +### `OAuth2PasswordRequestForm` + +🥇, 🗄 `OAuth2PasswordRequestForm`, & ⚙️ ⚫️ 🔗 ⏮️ `Depends` *➡ 🛠️* `/token`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="4 76" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="2 74" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +`OAuth2PasswordRequestForm` 🎓 🔗 👈 📣 📨 💪 ⏮️: + +* `username`. +* `password`. +* 📦 `scope` 🏑 🦏 🎻, ✍ 🎻 🎏 🚀. +* 📦 `grant_type`. + +!!! tip + Oauth2️⃣ 🔌 🤙 *🚚* 🏑 `grant_type` ⏮️ 🔧 💲 `password`, ✋️ `OAuth2PasswordRequestForm` 🚫 🛠️ ⚫️. + + 🚥 👆 💪 🛠️ ⚫️, ⚙️ `OAuth2PasswordRequestFormStrict` ↩️ `OAuth2PasswordRequestForm`. + +* 📦 `client_id` (👥 🚫 💪 ⚫️ 👆 🖼). +* 📦 `client_secret` (👥 🚫 💪 ⚫️ 👆 🖼). + +!!! info + `OAuth2PasswordRequestForm` 🚫 🎁 🎓 **FastAPI** `OAuth2PasswordBearer`. + + `OAuth2PasswordBearer` ⚒ **FastAPI** 💭 👈 ⚫️ 💂‍♂ ⚖. ⚫️ 🚮 👈 🌌 🗄. + + ✋️ `OAuth2PasswordRequestForm` 🎓 🔗 👈 👆 💪 ✔️ ✍ 👆, ⚖️ 👆 💪 ✔️ 📣 `Form` 🔢 🔗. + + ✋️ ⚫️ ⚠ ⚙️ 💼, ⚫️ 🚚 **FastAPI** 🔗, ⚒ ⚫️ ⏩. + +### ⚙️ 📨 💽 + +!!! tip + 👐 🔗 🎓 `OAuth2PasswordRequestForm` 🏆 🚫 ✔️ 🔢 `scope` ⏮️ 📏 🎻 👽 🚀, ↩️, ⚫️ 🔜 ✔️ `scopes` 🔢 ⏮️ ☑ 📇 🎻 🔠 ↔ 📨. + + 👥 🚫 ⚙️ `scopes` 👉 🖼, ✋️ 🛠️ 📤 🚥 👆 💪 ⚫️. + +🔜, 🤚 👩‍💻 📊 ⚪️➡️ (❌) 💽, ⚙️ `username` ⚪️➡️ 📨 🏑. + +🚥 📤 🙅‍♂ ✅ 👩‍💻, 👥 📨 ❌ 💬 "❌ 🆔 ⚖️ 🔐". + +❌, 👥 ⚙️ ⚠ `HTTPException`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3 77-79" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1 75-77" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +### ✅ 🔐 + +👉 ☝ 👥 ✔️ 👩‍💻 📊 ⚪️➡️ 👆 💽, ✋️ 👥 🚫 ✅ 🔐. + +➡️ 🚮 👈 💽 Pydantic `UserInDB` 🏷 🥇. + +👆 🔜 🙅 🖊 🔢 🔐,, 👥 🔜 ⚙️ (❌) 🔐 🔁 ⚙️. + +🚥 🔐 🚫 🏏, 👥 📨 🎏 ❌. + +#### 🔐 🔁 + +"🔁" ⛓: 🏭 🎚 (🔐 👉 💼) 🔘 🔁 🔢 (🎻) 👈 👀 💖 🙃. + +🕐❔ 👆 🚶‍♀️ ⚫️❔ 🎏 🎚 (⚫️❔ 🎏 🔐) 👆 🤚 ⚫️❔ 🎏 🙃. + +✋️ 👆 🚫🔜 🗜 ⚪️➡️ 🙃 🔙 🔐. + +##### ⚫️❔ ⚙️ 🔐 🔁 + +🚥 👆 💽 📎, 🧙‍♀ 🏆 🚫 ✔️ 👆 👩‍💻' 🔢 🔐, 🕴#️⃣. + +, 🧙‍♀ 🏆 🚫 💪 🔄 ⚙️ 👈 🎏 🔐 ➕1️⃣ ⚙️ (📚 👩‍💻 ⚙️ 🎏 🔐 🌐, 👉 🔜 ⚠). + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="80-83" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="78-81" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +#### 🔃 `**user_dict` + +`UserInDB(**user_dict)` ⛓: + +*🚶‍♀️ 🔑 & 💲 `user_dict` 🔗 🔑-💲 ❌, 🌓:* + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +!!! info + 🌅 🏁 🔑 `**👩‍💻_ #️⃣ ` ✅ 🔙 [🧾 **➕ 🏷**](../extra-models.md#about-user_indict){.internal-link target=_blank}. + +## 📨 🤝 + +📨 `token` 🔗 🔜 🎻 🎚. + +⚫️ 🔜 ✔️ `token_type`. 👆 💼, 👥 ⚙️ "📨" 🤝, 🤝 🆎 🔜 "`bearer`". + +& ⚫️ 🔜 ✔️ `access_token`, ⏮️ 🎻 ⚗ 👆 🔐 🤝. + +👉 🙅 🖼, 👥 🔜 🍕 😟 & 📨 🎏 `username` 🤝. + +!!! tip + ⏭ 📃, 👆 🔜 👀 🎰 🔐 🛠️, ⏮️ 🔐 #️⃣ & 🥙 🤝. + + ✋️ 🔜, ➡️ 🎯 🔛 🎯 ℹ 👥 💪. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="85" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="83" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +!!! tip + 🔌, 👆 🔜 📨 🎻 ⏮️ `access_token` & `token_type`, 🎏 👉 🖼. + + 👉 🕳 👈 👆 ✔️ 👆 👆 📟, & ⚒ 💭 👆 ⚙️ 📚 🎻 🔑. + + ⚫️ 🌖 🕴 👜 👈 👆 ✔️ 💭 ☑ 👆, 🛠️ ⏮️ 🔧. + + 🎂, **FastAPI** 🍵 ⚫️ 👆. + +## ℹ 🔗 + +🔜 👥 🔜 ℹ 👆 🔗. + +👥 💚 🤚 `current_user` *🕴* 🚥 👉 👩‍💻 🦁. + +, 👥 ✍ 🌖 🔗 `get_current_active_user` 👈 🔄 ⚙️ `get_current_user` 🔗. + +👯‍♂️ 👉 🔗 🔜 📨 🇺🇸🔍 ❌ 🚥 👩‍💻 🚫 🔀, ⚖️ 🚥 🔕. + +, 👆 🔗, 👥 🔜 🕴 🤚 👩‍💻 🚥 👩‍💻 🔀, ☑ 🔓, & 🦁: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="58-66 69-72 90" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="55-64 67-70 88" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +!!! info + 🌖 🎚 `WWW-Authenticate` ⏮️ 💲 `Bearer` 👥 🛬 📥 🍕 🔌. + + 🙆 🇺🇸🔍 (❌) 👔 📟 4️⃣0️⃣1️⃣ "⛔" 🤔 📨 `WWW-Authenticate` 🎚. + + 💼 📨 🤝 (👆 💼), 💲 👈 🎚 🔜 `Bearer`. + + 👆 💪 🤙 🚶 👈 ➕ 🎚 & ⚫️ 🔜 👷. + + ✋️ ⚫️ 🚚 📥 🛠️ ⏮️ 🔧. + + , 📤 5️⃣📆 🧰 👈 ⌛ & ⚙️ ⚫️ (🔜 ⚖️ 🔮) & 👈 💪 ⚠ 👆 ⚖️ 👆 👩‍💻, 🔜 ⚖️ 🔮. + + 👈 💰 🐩... + +## 👀 ⚫️ 🎯 + +📂 🎓 🩺: http://127.0.0.1:8000/docs. + +### 🔓 + +🖊 "✔" 🔼. + +⚙️ 🎓: + +👩‍💻: `johndoe` + +🔐: `secret` + + + +⏮️ 🔗 ⚙️, 👆 🔜 👀 ⚫️ 💖: + + + +### 🤚 👆 👍 👩‍💻 💽 + +🔜 ⚙️ 🛠️ `GET` ⏮️ ➡ `/users/me`. + +👆 🔜 🤚 👆 👩‍💻 📊, 💖: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +🚥 👆 🖊 🔒 ℹ & ⏏, & ⤴️ 🔄 🎏 🛠️ 🔄, 👆 🔜 🤚 🇺🇸🔍 4️⃣0️⃣1️⃣ ❌: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### 🔕 👩‍💻 + +🔜 🔄 ⏮️ 🔕 👩‍💻, 🔓 ⏮️: + +👩‍💻: `alice` + +🔐: `secret2` + +& 🔄 ⚙️ 🛠️ `GET` ⏮️ ➡ `/users/me`. + +👆 🔜 🤚 "🔕 👩‍💻" ❌, 💖: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## 🌃 + +👆 🔜 ✔️ 🧰 🛠️ 🏁 💂‍♂ ⚙️ ⚓️ 🔛 `username` & `password` 👆 🛠️. + +⚙️ 👫 🧰, 👆 💪 ⚒ 💂‍♂ ⚙️ 🔗 ⏮️ 🙆 💽 & ⏮️ 🙆 👩‍💻 ⚖️ 💽 🏷. + +🕴 ℹ ❌ 👈 ⚫️ 🚫 🤙 "🔐". + +⏭ 📃 👆 🔜 👀 ❔ ⚙️ 🔐 🔐 🔁 🗃 & 🥙 🤝. diff --git a/docs/em/docs/tutorial/sql-databases.md b/docs/em/docs/tutorial/sql-databases.md new file mode 100644 index 0000000000..9d46c24608 --- /dev/null +++ b/docs/em/docs/tutorial/sql-databases.md @@ -0,0 +1,786 @@ +# 🗄 (🔗) 💽 + +**FastAPI** 🚫 🚚 👆 ⚙️ 🗄 (🔗) 💽. + +✋️ 👆 💪 ⚙️ 🙆 🔗 💽 👈 👆 💚. + +📥 👥 🔜 👀 🖼 ⚙️ 🇸🇲. + +👆 💪 💪 🛠️ ⚫️ 🙆 💽 🐕‍🦺 🇸🇲, 💖: + +* ✳ +* ✳ +* 🗄 +* 🐸 +* 🤸‍♂ 🗄 💽, ♒️. + +👉 🖼, 👥 🔜 ⚙️ **🗄**, ↩️ ⚫️ ⚙️ 👁 📁 & 🐍 ✔️ 🛠️ 🐕‍🦺. , 👆 💪 📁 👉 🖼 & 🏃 ⚫️. + +⏪, 👆 🏭 🈸, 👆 💪 💚 ⚙️ 💽 💽 💖 **✳**. + +!!! tip + 📤 🛂 🏗 🚂 ⏮️ **FastAPI** & **✳**, 🌐 ⚓️ 🔛 **☁**, 🔌 🕸 & 🌖 🧰: https://github.com/tiangolo/full-stack-fastapi-postgresql + +!!! note + 👀 👈 📚 📟 🐩 `SQLAlchemy` 📟 👆 🔜 ⚙️ ⏮️ 🙆 🛠️. + + **FastAPI** 🎯 📟 🤪 🕧. + +## 🐜 + +**FastAPI** 👷 ⏮️ 🙆 💽 & 🙆 👗 🗃 💬 💽. + +⚠ ⚓ ⚙️ "🐜": "🎚-🔗 🗺" 🗃. + +🐜 ✔️ 🧰 🗜 ("*🗺*") 🖖 *🎚* 📟 & 💽 🏓 ("*🔗*"). + +⏮️ 🐜, 👆 🛎 ✍ 🎓 👈 🎨 🏓 🗄 💽, 🔠 🔢 🎓 🎨 🏓, ⏮️ 📛 & 🆎. + +🖼 🎓 `Pet` 💪 🎨 🗄 🏓 `pets`. + +& 🔠 *👐* 🎚 👈 🎓 🎨 ⏭ 💽. + +🖼 🎚 `orion_cat` (👐 `Pet`) 💪 ✔️ 🔢 `orion_cat.type`, 🏓 `type`. & 💲 👈 🔢 💪, ✅ `"cat"`. + +👫 🐜 ✔️ 🧰 ⚒ 🔗 ⚖️ 🔗 🖖 🏓 ⚖️ 👨‍💼. + +👉 🌌, 👆 💪 ✔️ 🔢 `orion_cat.owner` & 👨‍💼 🔜 🔌 💽 👉 🐶 👨‍💼, ✊ ⚪️➡️ 🏓 *👨‍💼*. + +, `orion_cat.owner.name` 💪 📛 (⚪️➡️ `name` 🏓 `owners` 🏓) 👉 🐶 👨‍💼. + +⚫️ 💪 ✔️ 💲 💖 `"Arquilian"`. + +& 🐜 🔜 🌐 👷 🤚 ℹ ⚪️➡️ 🔗 🏓 *👨‍💼* 🕐❔ 👆 🔄 🔐 ⚫️ ⚪️➡️ 👆 🐶 🎚. + +⚠ 🐜 🖼: ✳-🐜 (🍕 ✳ 🛠️), 🇸🇲 🐜 (🍕 🇸🇲, 🔬 🛠️) & 🏒 (🔬 🛠️), 👪 🎏. + +📥 👥 🔜 👀 ❔ 👷 ⏮️ **🇸🇲 🐜**. + +🎏 🌌 👆 💪 ⚙️ 🙆 🎏 🐜. + +!!! tip + 📤 🌓 📄 ⚙️ 🏒 📥 🩺. + +## 📁 📊 + +👫 🖼, ➡️ 💬 👆 ✔️ 📁 📛 `my_super_project` 👈 🔌 🎧-📁 🤙 `sql_app` ⏮️ 📊 💖 👉: + +``` +. +└── sql_app + ├── __init__.py + ├── crud.py + ├── database.py + ├── main.py + ├── models.py + └── schemas.py +``` + +📁 `__init__.py` 🛁 📁, ✋️ ⚫️ 💬 🐍 👈 `sql_app` ⏮️ 🌐 🚮 🕹 (🐍 📁) 📦. + +🔜 ➡️ 👀 ⚫️❔ 🔠 📁/🕹 🔨. + +## ❎ `SQLAlchemy` + +🥇 👆 💪 ❎ `SQLAlchemy`: + +
+ +```console +$ pip install sqlalchemy + +---> 100% +``` + +
+ +## ✍ 🇸🇲 🍕 + +➡️ 🔗 📁 `sql_app/database.py`. + +### 🗄 🇸🇲 🍕 + +```Python hl_lines="1-3" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +### ✍ 💽 📛 🇸🇲 + +```Python hl_lines="5-6" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +👉 🖼, 👥 "🔗" 🗄 💽 (📂 📁 ⏮️ 🗄 💽). + +📁 🔜 🔎 🎏 📁 📁 `sql_app.db`. + +👈 ⚫️❔ 🏁 🍕 `./sql_app.db`. + +🚥 👆 ⚙️ **✳** 💽 ↩️, 👆 🔜 ✔️ ✍ ⏸: + +```Python +SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" +``` + +...& 🛠️ ⚫️ ⏮️ 👆 💽 📊 & 🎓 (📊 ✳, ✳ ⚖️ 🙆 🎏). + +!!! tip + + 👉 👑 ⏸ 👈 👆 🔜 ✔️ 🔀 🚥 👆 💚 ⚙️ 🎏 💽. + +### ✍ 🇸🇲 `engine` + +🥇 🔁 ✍ 🇸🇲 "🚒". + +👥 🔜 ⏪ ⚙️ 👉 `engine` 🎏 🥉. + +```Python hl_lines="8-10" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +#### 🗒 + +❌: + +```Python +connect_args={"check_same_thread": False} +``` + +...💪 🕴 `SQLite`. ⚫️ 🚫 💪 🎏 💽. + +!!! info "📡 ℹ" + + 🔢 🗄 🔜 🕴 ✔ 1️⃣ 🧵 🔗 ⏮️ ⚫️, 🤔 👈 🔠 🧵 🔜 🍵 🔬 📨. + + 👉 ❎ 😫 🤝 🎏 🔗 🎏 👜 (🎏 📨). + + ✋️ FastAPI, ⚙️ 😐 🔢 (`def`) 🌅 🌘 1️⃣ 🧵 💪 🔗 ⏮️ 💽 🎏 📨, 👥 💪 ⚒ 🗄 💭 👈 ⚫️ 🔜 ✔ 👈 ⏮️ `connect_args={"check_same_thread": False}`. + + , 👥 🔜 ⚒ 💭 🔠 📨 🤚 🚮 👍 💽 🔗 🎉 🔗, 📤 🙅‍♂ 💪 👈 🔢 🛠️. + +### ✍ `SessionLocal` 🎓 + +🔠 👐 `SessionLocal` 🎓 🔜 💽 🎉. 🎓 ⚫️ 🚫 💽 🎉. + +✋️ 🕐 👥 ✍ 👐 `SessionLocal` 🎓, 👉 👐 🔜 ☑ 💽 🎉. + +👥 📛 ⚫️ `SessionLocal` 🔬 ⚫️ ⚪️➡️ `Session` 👥 🏭 ⚪️➡️ 🇸🇲. + +👥 🔜 ⚙️ `Session` (1️⃣ 🗄 ⚪️➡️ 🇸🇲) ⏪. + +✍ `SessionLocal` 🎓, ⚙️ 🔢 `sessionmaker`: + +```Python hl_lines="11" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +### ✍ `Base` 🎓 + +🔜 👥 🔜 ⚙️ 🔢 `declarative_base()` 👈 📨 🎓. + +⏪ 👥 🔜 😖 ⚪️➡️ 👉 🎓 ✍ 🔠 💽 🏷 ⚖️ 🎓 (🐜 🏷): + +```Python hl_lines="13" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +## ✍ 💽 🏷 + +➡️ 🔜 👀 📁 `sql_app/models.py`. + +### ✍ 🇸🇲 🏷 ⚪️➡️ `Base` 🎓 + +👥 🔜 ⚙️ 👉 `Base` 🎓 👥 ✍ ⏭ ✍ 🇸🇲 🏷. + +!!! tip + 🇸🇲 ⚙️ ⚖ "**🏷**" 🔗 👉 🎓 & 👐 👈 🔗 ⏮️ 💽. + + ✋️ Pydantic ⚙️ ⚖ "**🏷**" 🔗 🕳 🎏, 💽 🔬, 🛠️, & 🧾 🎓 & 👐. + +🗄 `Base` ⚪️➡️ `database` (📁 `database.py` ⚪️➡️ 🔛). + +✍ 🎓 👈 😖 ⚪️➡️ ⚫️. + +👫 🎓 🇸🇲 🏷. + +```Python hl_lines="4 7-8 18-19" +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +`__tablename__` 🔢 💬 🇸🇲 📛 🏓 ⚙️ 💽 🔠 👫 🏷. + +### ✍ 🏷 🔢/🏓 + +🔜 ✍ 🌐 🏷 (🎓) 🔢. + +🔠 👫 🔢 🎨 🏓 🚮 🔗 💽 🏓. + +👥 ⚙️ `Column` ⚪️➡️ 🇸🇲 🔢 💲. + +& 👥 🚶‍♀️ 🇸🇲 🎓 "🆎", `Integer`, `String`, & `Boolean`, 👈 🔬 🆎 💽, ❌. + +```Python hl_lines="1 10-13 21-24" +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +### ✍ 💛 + +🔜 ✍ 💛. + +👉, 👥 ⚙️ `relationship` 🚚 🇸🇲 🐜. + +👉 🔜 ▶️️, 🌅 ⚖️ 🌘, "🎱" 🔢 👈 🔜 🔌 💲 ⚪️➡️ 🎏 🏓 🔗 👉 1️⃣. + +```Python hl_lines="2 15 26" +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +🕐❔ 🔐 🔢 `items` `User`, `my_user.items`, ⚫️ 🔜 ✔️ 📇 `Item` 🇸🇲 🏷 (⚪️➡️ `items` 🏓) 👈 ✔️ 💱 🔑 ☝ 👉 ⏺ `users` 🏓. + +🕐❔ 👆 🔐 `my_user.items`, 🇸🇲 🔜 🤙 🚶 & ☕ 🏬 ⚪️➡️ 💽 `items` 🏓 & 🔗 👫 📥. + +& 🕐❔ 🔐 🔢 `owner` `Item`, ⚫️ 🔜 🔌 `User` 🇸🇲 🏷 ⚪️➡️ `users` 🏓. ⚫️ 🔜 ⚙️ `owner_id` 🔢/🏓 ⏮️ 🚮 💱 🔑 💭 ❔ ⏺ 🤚 ⚪️➡️ `users` 🏓. + +## ✍ Pydantic 🏷 + +🔜 ➡️ ✅ 📁 `sql_app/schemas.py`. + +!!! tip + ❎ 😨 🖖 🇸🇲 *🏷* & Pydantic *🏷*, 👥 🔜 ✔️ 📁 `models.py` ⏮️ 🇸🇲 🏷, & 📁 `schemas.py` ⏮️ Pydantic 🏷. + + 👫 Pydantic 🏷 🔬 🌅 ⚖️ 🌘 "🔗" (☑ 📊 💠). + + 👉 🔜 ℹ 👥 ❎ 😨 ⏪ ⚙️ 👯‍♂️. + +### ✍ ▶️ Pydantic *🏷* / 🔗 + +✍ `ItemBase` & `UserBase` Pydantic *🏷* (⚖️ ➡️ 💬 "🔗") ✔️ ⚠ 🔢 ⏪ 🏗 ⚖️ 👂 📊. + +& ✍ `ItemCreate` & `UserCreate` 👈 😖 ⚪️➡️ 👫 (👫 🔜 ✔️ 🎏 🔢), ➕ 🙆 🌖 📊 (🔢) 💪 🏗. + +, 👩‍💻 🔜 ✔️ `password` 🕐❔ 🏗 ⚫️. + +✋️ 💂‍♂, `password` 🏆 🚫 🎏 Pydantic *🏷*, 🖼, ⚫️ 🏆 🚫 📨 ⚪️➡️ 🛠️ 🕐❔ 👂 👩‍💻. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="3 6-8 11-12 23-24 27-28" + {!> ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="3 6-8 11-12 23-24 27-28" + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="1 4-6 9-10 21-22 25-26" + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` + +#### 🇸🇲 👗 & Pydantic 👗 + +👀 👈 🇸🇲 *🏷* 🔬 🔢 ⚙️ `=`, & 🚶‍♀️ 🆎 🔢 `Column`, 💖: + +```Python +name = Column(String) +``` + +⏪ Pydantic *🏷* 📣 🆎 ⚙️ `:`, 🆕 🆎 ✍ ❕/🆎 🔑: + +```Python +name: str +``` + +✔️ ⚫️ 🤯, 👆 🚫 🤚 😕 🕐❔ ⚙️ `=` & `:` ⏮️ 👫. + +### ✍ Pydantic *🏷* / 🔗 👂 / 📨 + +🔜 ✍ Pydantic *🏷* (🔗) 👈 🔜 ⚙️ 🕐❔ 👂 💽, 🕐❔ 🛬 ⚫️ ⚪️➡️ 🛠️. + +🖼, ⏭ 🏗 🏬, 👥 🚫 💭 ⚫️❔ 🔜 🆔 🛠️ ⚫️, ✋️ 🕐❔ 👂 ⚫️ (🕐❔ 🛬 ⚫️ ⚪️➡️ 🛠️) 👥 🔜 ⏪ 💭 🚮 🆔. + +🎏 🌌, 🕐❔ 👂 👩‍💻, 👥 💪 🔜 📣 👈 `items` 🔜 🔌 🏬 👈 💭 👉 👩‍💻. + +🚫 🕴 🆔 📚 🏬, ✋️ 🌐 💽 👈 👥 🔬 Pydantic *🏷* 👂 🏬: `Item`. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="15-17 31-34" + {!> ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="15-17 31-34" + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="13-15 29-32" + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` + +!!! tip + 👀 👈 `User`, Pydantic *🏷* 👈 🔜 ⚙️ 🕐❔ 👂 👩‍💻 (🛬 ⚫️ ⚪️➡️ 🛠️) 🚫 🔌 `password`. + +### ⚙️ Pydantic `orm_mode` + +🔜, Pydantic *🏷* 👂, `Item` & `User`, 🚮 🔗 `Config` 🎓. + +👉 `Config` 🎓 ⚙️ 🚚 📳 Pydantic. + +`Config` 🎓, ⚒ 🔢 `orm_mode = True`. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="15 19-20 31 36-37" + {!> ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="15 19-20 31 36-37" + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python hl_lines="13 17-18 29 34-35" + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` + +!!! tip + 👀 ⚫️ ⚖ 💲 ⏮️ `=`, 💖: + + `orm_mode = True` + + ⚫️ 🚫 ⚙️ `:` 🆎 📄 ⏭. + + 👉 ⚒ 📁 💲, 🚫 📣 🆎. + +Pydantic `orm_mode` 🔜 💬 Pydantic *🏷* ✍ 💽 🚥 ⚫️ 🚫 `dict`, ✋️ 🐜 🏷 (⚖️ 🙆 🎏 ❌ 🎚 ⏮️ 🔢). + +👉 🌌, ↩️ 🕴 🔄 🤚 `id` 💲 ⚪️➡️ `dict`,: + +```Python +id = data["id"] +``` + +⚫️ 🔜 🔄 🤚 ⚫️ ⚪️➡️ 🔢,: + +```Python +id = data.id +``` + +& ⏮️ 👉, Pydantic *🏷* 🔗 ⏮️ 🐜, & 👆 💪 📣 ⚫️ `response_model` ❌ 👆 *➡ 🛠️*. + +👆 🔜 💪 📨 💽 🏷 & ⚫️ 🔜 ✍ 💽 ⚪️➡️ ⚫️. + +#### 📡 ℹ 🔃 🐜 📳 + +🇸🇲 & 📚 🎏 🔢 "🙃 🚚". + +👈 ⛓, 🖼, 👈 👫 🚫 ☕ 💽 💛 ⚪️➡️ 💽 🚥 👆 🔄 🔐 🔢 👈 🔜 🔌 👈 💽. + +🖼, 🔐 🔢 `items`: + +```Python +current_user.items +``` + +🔜 ⚒ 🇸🇲 🚶 `items` 🏓 & 🤚 🏬 👉 👩‍💻, ✋️ 🚫 ⏭. + +🍵 `orm_mode`, 🚥 👆 📨 🇸🇲 🏷 ⚪️➡️ 👆 *➡ 🛠️*, ⚫️ 🚫🔜 🔌 💛 💽. + +🚥 👆 📣 📚 💛 👆 Pydantic 🏷. + +✋️ ⏮️ 🐜 📳, Pydantic ⚫️ 🔜 🔄 🔐 💽 ⚫️ 💪 ⚪️➡️ 🔢 (↩️ 🤔 `dict`), 👆 💪 📣 🎯 💽 👆 💚 📨 & ⚫️ 🔜 💪 🚶 & 🤚 ⚫️, ⚪️➡️ 🐜. + +## 💩 🇨🇻 + +🔜 ➡️ 👀 📁 `sql_app/crud.py`. + +👉 📁 👥 🔜 ✔️ ♻ 🔢 🔗 ⏮️ 💽 💽. + +**💩** 👟 ⚪️➡️: **🅱**📧, **Ⓜ**💳, **👤** = , & **🇨🇮**📧. + +...👐 👉 🖼 👥 🕴 🏗 & 👂. + +### ✍ 💽 + +🗄 `Session` ⚪️➡️ `sqlalchemy.orm`, 👉 🔜 ✔ 👆 📣 🆎 `db` 🔢 & ✔️ 👻 🆎 ✅ & 🛠️ 👆 🔢. + +🗄 `models` (🇸🇲 🏷) & `schemas` (Pydantic *🏷* / 🔗). + +✍ 🚙 🔢: + +* ✍ 👁 👩‍💻 🆔 & 📧. +* ✍ 💗 👩‍💻. +* ✍ 💗 🏬. + +```Python hl_lines="1 3 6-7 10-11 14-15 27-28" +{!../../../docs_src/sql_databases/sql_app/crud.py!} +``` + +!!! tip + 🏗 🔢 👈 🕴 💡 🔗 ⏮️ 💽 (🤚 👩‍💻 ⚖️ 🏬) 🔬 👆 *➡ 🛠️ 🔢*, 👆 💪 🌖 💪 ♻ 👫 💗 🍕 & 🚮 ⚒ 💯 👫. + +### ✍ 💽 + +🔜 ✍ 🚙 🔢 ✍ 💽. + +🔁: + +* ✍ 🇸🇲 🏷 *👐* ⏮️ 👆 📊. +* `add` 👈 👐 🎚 👆 💽 🎉. +* `commit` 🔀 💽 (👈 👫 🖊). +* `refresh` 👆 👐 (👈 ⚫️ 🔌 🙆 🆕 📊 ⚪️➡️ 💽, 💖 🏗 🆔). + +```Python hl_lines="18-24 31-36" +{!../../../docs_src/sql_databases/sql_app/crud.py!} +``` + +!!! tip + 🇸🇲 🏷 `User` 🔌 `hashed_password` 👈 🔜 🔌 🔐 #️⃣ ⏬ 🔐. + + ✋️ ⚫️❔ 🛠️ 👩‍💻 🚚 ⏮️ 🔐, 👆 💪 ⚗ ⚫️ & 🏗 #️⃣ 🔐 👆 🈸. + + & ⤴️ 🚶‍♀️ `hashed_password` ❌ ⏮️ 💲 🖊. + +!!! warning + 👉 🖼 🚫 🔐, 🔐 🚫#️⃣. + + 🎰 👨‍❤‍👨 🈸 👆 🔜 💪 #️⃣ 🔐 & 🙅 🖊 👫 🔢. + + 🌅 ℹ, 🚶 🔙 💂‍♂ 📄 🔰. + + 📥 👥 🎯 🕴 🔛 🧰 & 👨‍🔧 💽. + +!!! tip + ↩️ 🚶‍♀️ 🔠 🇨🇻 ❌ `Item` & 👂 🔠 1️⃣ 👫 ⚪️➡️ Pydantic *🏷*, 👥 🏭 `dict` ⏮️ Pydantic *🏷*'Ⓜ 📊 ⏮️: + + `item.dict()` + + & ⤴️ 👥 🚶‍♀️ `dict`'Ⓜ 🔑-💲 👫 🇨🇻 ❌ 🇸🇲 `Item`, ⏮️: + + `Item(**item.dict())` + + & ⤴️ 👥 🚶‍♀️ ➕ 🇨🇻 ❌ `owner_id` 👈 🚫 🚚 Pydantic *🏷*, ⏮️: + + `Item(**item.dict(), owner_id=user_id)` + +## 👑 **FastAPI** 📱 + +& 🔜 📁 `sql_app/main.py` ➡️ 🛠️ & ⚙️ 🌐 🎏 🍕 👥 ✍ ⏭. + +### ✍ 💽 🏓 + +📶 🙃 🌌 ✍ 💽 🏓: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="9" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="7" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` + +#### ⚗ 🗒 + +🛎 👆 🔜 🎲 🔢 👆 💽 (✍ 🏓, ♒️) ⏮️ . + +& 👆 🔜 ⚙️ ⚗ "🛠️" (👈 🚮 👑 👨‍🏭). + +"🛠️" ⚒ 🔁 💪 🕐❔ 👆 🔀 📊 👆 🇸🇲 🏷, 🚮 🆕 🔢, ♒️. 🔁 👈 🔀 💽, 🚮 🆕 🏓, 🆕 🏓, ♒️. + +👆 💪 🔎 🖼 ⚗ FastAPI 🏗 📄 ⚪️➡️ [🏗 ⚡ - 📄](../project-generation.md){.internal-link target=_blank}. 🎯 `alembic` 📁 ℹ 📟. + +### ✍ 🔗 + +🔜 ⚙️ `SessionLocal` 🎓 👥 ✍ `sql_app/database.py` 📁 ✍ 🔗. + +👥 💪 ✔️ 🔬 💽 🎉/🔗 (`SessionLocal`) 📍 📨, ⚙️ 🎏 🎉 🔘 🌐 📨 & ⤴️ 🔐 ⚫️ ⏮️ 📨 🏁. + +& ⤴️ 🆕 🎉 🔜 ✍ ⏭ 📨. + +👈, 👥 🔜 ✍ 🆕 🔗 ⏮️ `yield`, 🔬 ⏭ 📄 🔃 [🔗 ⏮️ `yield`](dependencies/dependencies-with-yield.md){.internal-link target=_blank}. + +👆 🔗 🔜 ✍ 🆕 🇸🇲 `SessionLocal` 👈 🔜 ⚙️ 👁 📨, & ⤴️ 🔐 ⚫️ 🕐 📨 🏁. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="15-20" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="13-18" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` + +!!! info + 👥 🚮 🏗 `SessionLocal()` & 🚚 📨 `try` 🍫. + + & ⤴️ 👥 🔐 ⚫️ `finally` 🍫. + + 👉 🌌 👥 ⚒ 💭 💽 🎉 🕧 📪 ⏮️ 📨. 🚥 📤 ⚠ ⏪ 🏭 📨. + + ✋️ 👆 💪 🚫 🤚 ➕1️⃣ ⚠ ⚪️➡️ 🚪 📟 (⏮️ `yield`). 👀 🌖 [🔗 ⏮️ `yield` & `HTTPException`](./dependencies/dependencies-with-yield.md#dependencies-with-yield-and-httpexception){.internal-link target=_blank} + +& ⤴️, 🕐❔ ⚙️ 🔗 *➡ 🛠️ 🔢*, 👥 📣 ⚫️ ⏮️ 🆎 `Session` 👥 🗄 🔗 ⚪️➡️ 🇸🇲. + +👉 🔜 ⤴️ 🤝 👥 👍 👨‍🎨 🐕‍🦺 🔘 *➡ 🛠️ 🔢*, ↩️ 👨‍🎨 🔜 💭 👈 `db` 🔢 🆎 `Session`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="24 32 38 47 53" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="22 30 36 45 51" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` + +!!! info "📡 ℹ" + 🔢 `db` 🤙 🆎 `SessionLocal`, ✋️ 👉 🎓 (✍ ⏮️ `sessionmaker()`) "🗳" 🇸🇲 `Session`,, 👨‍🎨 🚫 🤙 💭 ⚫️❔ 👩‍🔬 🚚. + + ✋️ 📣 🆎 `Session`, 👨‍🎨 🔜 💪 💭 💪 👩‍🔬 (`.add()`, `.query()`, `.commit()`, ♒️) & 💪 🚚 👍 🐕‍🦺 (💖 🛠️). 🆎 📄 🚫 📉 ☑ 🎚. + +### ✍ 👆 **FastAPI** *➡ 🛠️* + +🔜, 😒, 📥 🐩 **FastAPI** *➡ 🛠️* 📟. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="23-28 31-34 37-42 45-49 52-55" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="21-26 29-32 35-40 43-47 50-53" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` + +👥 🏗 💽 🎉 ⏭ 🔠 📨 🔗 ⏮️ `yield`, & ⤴️ 📪 ⚫️ ⏮️. + +& ⤴️ 👥 💪 ✍ 🚚 🔗 *➡ 🛠️ 🔢*, 🤚 👈 🎉 🔗. + +⏮️ 👈, 👥 💪 🤙 `crud.get_user` 🔗 ⚪️➡️ 🔘 *➡ 🛠️ 🔢* & ⚙️ 👈 🎉. + +!!! tip + 👀 👈 💲 👆 📨 🇸🇲 🏷, ⚖️ 📇 🇸🇲 🏷. + + ✋️ 🌐 *➡ 🛠️* ✔️ `response_model` ⏮️ Pydantic *🏷* / 🔗 ⚙️ `orm_mode`, 💽 📣 👆 Pydantic 🏷 🔜 ⚗ ⚪️➡️ 👫 & 📨 👩‍💻, ⏮️ 🌐 😐 ⛽ & 🔬. + +!!! tip + 👀 👈 📤 `response_models` 👈 ✔️ 🐩 🐍 🆎 💖 `List[schemas.Item]`. + + ✋️ 🎚/🔢 👈 `List` Pydantic *🏷* ⏮️ `orm_mode`, 💽 🔜 🗃 & 📨 👩‍💻 🛎, 🍵 ⚠. + +### 🔃 `def` 🆚 `async def` + +📥 👥 ⚙️ 🇸🇲 📟 🔘 *➡ 🛠️ 🔢* & 🔗, &, 🔄, ⚫️ 🔜 🚶 & 🔗 ⏮️ 🔢 💽. + +👈 💪 ⚠ 🚚 "⌛". + +✋️ 🇸🇲 🚫 ✔️ 🔗 ⚙️ `await` 🔗, 🔜 ⏮️ 🕳 💖: + +```Python +user = await db.query(User).first() +``` + +...& ↩️ 👥 ⚙️: + +```Python +user = db.query(User).first() +``` + +⤴️ 👥 🔜 📣 *➡ 🛠️ 🔢* & 🔗 🍵 `async def`, ⏮️ 😐 `def`,: + +```Python hl_lines="2" +@app.get("/users/{user_id}", response_model=schemas.User) +def read_user(user_id: int, db: Session = Depends(get_db)): + db_user = crud.get_user(db, user_id=user_id) + ... +``` + +!!! info + 🚥 👆 💪 🔗 👆 🔗 💽 🔁, 👀 [🔁 🗄 (🔗) 💽](../advanced/async-sql-databases.md){.internal-link target=_blank}. + +!!! note "📶 📡 ℹ" + 🚥 👆 😟 & ✔️ ⏬ 📡 💡, 👆 💪 ✅ 📶 📡 ℹ ❔ 👉 `async def` 🆚 `def` 🍵 [🔁](../async.md#very-technical-details){.internal-link target=_blank} 🩺. + +## 🛠️ + +↩️ 👥 ⚙️ 🇸🇲 🔗 & 👥 🚫 🚚 🙆 😇 🔌-⚫️ 👷 ⏮️ **FastAPI**, 👥 💪 🛠️ 💽 🛠️ ⏮️ 🔗. + +& 📟 🔗 🇸🇲 & 🇸🇲 🏷 🖖 🎏 🔬 📁, 👆 🔜 💪 🎭 🛠️ ⏮️ ⚗ 🍵 ✔️ ❎ FastAPI, Pydantic, ⚖️ 🕳 🙆. + +🎏 🌌, 👆 🔜 💪 ⚙️ 🎏 🇸🇲 🏷 & 🚙 🎏 🍕 👆 📟 👈 🚫 🔗 **FastAPI**. + +🖼, 🖥 📋 👨‍🏭 ⏮️ 🥒, 🅿, ⚖️ 📶. + +## 📄 🌐 📁 + + 💭 👆 🔜 ✔️ 📁 📛 `my_super_project` 👈 🔌 🎧-📁 🤙 `sql_app`. + +`sql_app` 🔜 ✔️ 📄 📁: + +* `sql_app/__init__.py`: 🛁 📁. + +* `sql_app/database.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +* `sql_app/models.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +* `sql_app/schemas.py`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` + +* `sql_app/crud.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/crud.py!} +``` + +* `sql_app/main.py`: + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` + +## ✅ ⚫️ + +👆 💪 📁 👉 📟 & ⚙️ ⚫️. + +!!! info + + 👐, 📟 🎦 📥 🍕 💯. 🌅 📟 👉 🩺. + +⤴️ 👆 💪 🏃 ⚫️ ⏮️ Uvicorn: + + +
+ +```console +$ uvicorn sql_app.main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +& ⤴️, 👆 💪 📂 👆 🖥 http://127.0.0.1:8000/docs. + +& 👆 🔜 💪 🔗 ⏮️ 👆 **FastAPI** 🈸, 👂 📊 ⚪️➡️ 🎰 💽: + + + +## 🔗 ⏮️ 💽 🔗 + +🚥 👆 💚 🔬 🗄 💽 (📁) 🔗, ➡ FastAPI, ℹ 🚮 🎚, 🚮 🏓, 🏓, ⏺, 🔀 📊, ♒️. 👆 💪 ⚙️ 💽 🖥 🗄. + +⚫️ 🔜 👀 💖 👉: + + + +👆 💪 ⚙️ 💳 🗄 🖥 💖 🗄 📋 ⚖️ ExtendsClass. + +## 🎛 💽 🎉 ⏮️ 🛠️ + +🚥 👆 💪 🚫 ⚙️ 🔗 ⏮️ `yield` - 🖼, 🚥 👆 🚫 ⚙️ **🐍 3️⃣.7️⃣** & 💪 🚫 ❎ "🐛" 🤔 🔛 **🐍 3️⃣.6️⃣** - 👆 💪 ⚒ 🆙 🎉 "🛠️" 🎏 🌌. + +"🛠️" 🌖 🔢 👈 🕧 🛠️ 🔠 📨, ⏮️ 📟 🛠️ ⏭, & 📟 🛠️ ⏮️ 🔗 🔢. + +### ✍ 🛠️ + +🛠️ 👥 🔜 🚮 (🔢) 🔜 ✍ 🆕 🇸🇲 `SessionLocal` 🔠 📨, 🚮 ⚫️ 📨 & ⤴️ 🔐 ⚫️ 🕐 📨 🏁. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python hl_lines="14-22" + {!> ../../../docs_src/sql_databases/sql_app/alt_main.py!} + ``` + +=== "🐍 3️⃣.9️⃣ & 🔛" + + ```Python hl_lines="12-20" + {!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!} + ``` + +!!! info + 👥 🚮 🏗 `SessionLocal()` & 🚚 📨 `try` 🍫. + + & ⤴️ 👥 🔐 ⚫️ `finally` 🍫. + + 👉 🌌 👥 ⚒ 💭 💽 🎉 🕧 📪 ⏮️ 📨. 🚥 📤 ⚠ ⏪ 🏭 📨. + +### 🔃 `request.state` + +`request.state` 🏠 🔠 `Request` 🎚. ⚫️ 📤 🏪 ❌ 🎚 📎 📨 ⚫️, 💖 💽 🎉 👉 💼. 👆 💪 ✍ 🌅 🔃 ⚫️ 💃 🩺 🔃 `Request` 🇵🇸. + +👥 👉 💼, ⚫️ ℹ 👥 🚚 👁 💽 🎉 ⚙️ 🔘 🌐 📨, & ⤴️ 🔐 ⏮️ (🛠️). + +### 🔗 ⏮️ `yield` ⚖️ 🛠️ + +❎ **🛠️** 📥 🎏 ⚫️❔ 🔗 ⏮️ `yield` 🔨, ⏮️ 🔺: + +* ⚫️ 🚚 🌖 📟 & 👄 🌅 🏗. +* 🛠️ ✔️ `async` 🔢. + * 🚥 📤 📟 ⚫️ 👈 ✔️ "⌛" 🕸, ⚫️ 💪 "🍫" 👆 🈸 📤 & 📉 🎭 🍖. + * 👐 ⚫️ 🎲 🚫 📶 ⚠ 📥 ⏮️ 🌌 `SQLAlchemy` 👷. + * ✋️ 🚥 👆 🚮 🌖 📟 🛠️ 👈 ✔️ 📚 👤/🅾 ⌛, ⚫️ 💪 ⤴️ ⚠. +* 🛠️ 🏃 *🔠* 📨. + * , 🔗 🔜 ✍ 🔠 📨. + * 🕐❔ *➡ 🛠️* 👈 🍵 👈 📨 🚫 💪 💽. + +!!! tip + ⚫️ 🎲 👍 ⚙️ 🔗 ⏮️ `yield` 🕐❔ 👫 🥃 ⚙️ 💼. + +!!! info + 🔗 ⏮️ `yield` 🚮 ⏳ **FastAPI**. + + ⏮️ ⏬ 👉 🔰 🕴 ✔️ 🖼 ⏮️ 🛠️ & 📤 🎲 📚 🈸 ⚙️ 🛠️ 💽 🎉 🧾. diff --git a/docs/em/docs/tutorial/static-files.md b/docs/em/docs/tutorial/static-files.md new file mode 100644 index 0000000000..6090c53385 --- /dev/null +++ b/docs/em/docs/tutorial/static-files.md @@ -0,0 +1,39 @@ +# 🎻 📁 + +👆 💪 🍦 🎻 📁 🔁 ⚪️➡️ 📁 ⚙️ `StaticFiles`. + +## ⚙️ `StaticFiles` + +* 🗄 `StaticFiles`. +* "🗻" `StaticFiles()` 👐 🎯 ➡. + +```Python hl_lines="2 6" +{!../../../docs_src/static_files/tutorial001.py!} +``` + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.staticfiles import StaticFiles`. + + **FastAPI** 🚚 🎏 `starlette.staticfiles` `fastapi.staticfiles` 🏪 👆, 👩‍💻. ✋️ ⚫️ 🤙 👟 🔗 ⚪️➡️ 💃. + +### ⚫️❔ "🗜" + +"🗜" ⛓ ❎ 🏁 "🔬" 🈸 🎯 ➡, 👈 ⤴️ ✊ 💅 🚚 🌐 🎧-➡. + +👉 🎏 ⚪️➡️ ⚙️ `APIRouter` 🗻 🈸 🍕 🔬. 🗄 & 🩺 ⚪️➡️ 👆 👑 🈸 🏆 🚫 🔌 🕳 ⚪️➡️ 🗻 🈸, ♒️. + +👆 💪 ✍ 🌅 🔃 👉 **🏧 👩‍💻 🦮**. + +## ℹ + +🥇 `"/static"` 🔗 🎧-➡ 👉 "🎧-🈸" 🔜 "🗻" 🔛. , 🙆 ➡ 👈 ▶️ ⏮️ `"/static"` 🔜 🍵 ⚫️. + +`directory="static"` 🔗 📛 📁 👈 🔌 👆 🎻 📁. + +`name="static"` 🤝 ⚫️ 📛 👈 💪 ⚙️ 🔘 **FastAPI**. + +🌐 👫 🔢 💪 🎏 🌘 "`static`", 🔆 👫 ⏮️ 💪 & 🎯 ℹ 👆 👍 🈸. + +## 🌅 ℹ + +🌖 ℹ & 🎛 ✅ 💃 🩺 🔃 🎻 📁. diff --git a/docs/em/docs/tutorial/testing.md b/docs/em/docs/tutorial/testing.md new file mode 100644 index 0000000000..999d67cd35 --- /dev/null +++ b/docs/em/docs/tutorial/testing.md @@ -0,0 +1,188 @@ +# 🔬 + +👏 💃, 🔬 **FastAPI** 🈸 ⏩ & 😌. + +⚫️ ⚓️ 🔛 🇸🇲, ❔ 🔄 🏗 ⚓️ 🔛 📨, ⚫️ 📶 😰 & 🏋️. + +⏮️ ⚫️, 👆 💪 ⚙️ 🔗 ⏮️ **FastAPI**. + +## ⚙️ `TestClient` + +!!! info + ⚙️ `TestClient`, 🥇 ❎ `httpx`. + + 🤶 Ⓜ. `pip install httpx`. + +🗄 `TestClient`. + +✍ `TestClient` 🚶‍♀️ 👆 **FastAPI** 🈸 ⚫️. + +✍ 🔢 ⏮️ 📛 👈 ▶️ ⏮️ `test_` (👉 🐩 `pytest` 🏛). + +⚙️ `TestClient` 🎚 🎏 🌌 👆 ⏮️ `httpx`. + +✍ 🙅 `assert` 📄 ⏮️ 🐩 🐍 🧬 👈 👆 💪 ✅ (🔄, 🐩 `pytest`). + +```Python hl_lines="2 12 15-18" +{!../../../docs_src/app_testing/tutorial001.py!} +``` + +!!! tip + 👀 👈 🔬 🔢 😐 `def`, 🚫 `async def`. + + & 🤙 👩‍💻 😐 🤙, 🚫 ⚙️ `await`. + + 👉 ✔ 👆 ⚙️ `pytest` 🔗 🍵 🤢. + +!!! note "📡 ℹ" + 👆 💪 ⚙️ `from starlette.testclient import TestClient`. + + **FastAPI** 🚚 🎏 `starlette.testclient` `fastapi.testclient` 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. + +!!! tip + 🚥 👆 💚 🤙 `async` 🔢 👆 💯 ↖️ ⚪️➡️ 📨 📨 👆 FastAPI 🈸 (✅ 🔁 💽 🔢), ✔️ 👀 [🔁 💯](../advanced/async-tests.md){.internal-link target=_blank} 🏧 🔰. + +## 🎏 💯 + +🎰 🈸, 👆 🎲 🔜 ✔️ 👆 💯 🎏 📁. + +& 👆 **FastAPI** 🈸 5️⃣📆 ✍ 📚 📁/🕹, ♒️. + +### **FastAPI** 📱 📁 + +➡️ 💬 👆 ✔️ 📁 📊 🔬 [🦏 🈸](./bigger-applications.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +📁 `main.py` 👆 ✔️ 👆 **FastAPI** 📱: + + +```Python +{!../../../docs_src/app_testing/main.py!} +``` + +### 🔬 📁 + +⤴️ 👆 💪 ✔️ 📁 `test_main.py` ⏮️ 👆 💯. ⚫️ 💪 🖖 🔛 🎏 🐍 📦 (🎏 📁 ⏮️ `__init__.py` 📁): + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +↩️ 👉 📁 🎏 📦, 👆 💪 ⚙️ ⚖ 🗄 🗄 🎚 `app` ⚪️➡️ `main` 🕹 (`main.py`): + +```Python hl_lines="3" +{!../../../docs_src/app_testing/test_main.py!} +``` + +...& ✔️ 📟 💯 💖 ⏭. + +## 🔬: ↔ 🖼 + +🔜 ➡️ ↔ 👉 🖼 & 🚮 🌖 ℹ 👀 ❔ 💯 🎏 🍕. + +### ↔ **FastAPI** 📱 📁 + +➡️ 😣 ⏮️ 🎏 📁 📊 ⏭: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +➡️ 💬 👈 🔜 📁 `main.py` ⏮️ 👆 **FastAPI** 📱 ✔️ 🎏 **➡ 🛠️**. + +⚫️ ✔️ `GET` 🛠️ 👈 💪 📨 ❌. + +⚫️ ✔️ `POST` 🛠️ 👈 💪 📨 📚 ❌. + +👯‍♂️ *➡ 🛠️* 🚚 `X-Token` 🎚. + +=== "🐍 3️⃣.6️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/app_testing/app_b/main.py!} + ``` + +=== "🐍 3️⃣.1️⃣0️⃣ & 🔛" + + ```Python + {!> ../../../docs_src/app_testing/app_b_py310/main.py!} + ``` + +### ↔ 🔬 📁 + +👆 💪 ⤴️ ℹ `test_main.py` ⏮️ ↔ 💯: + +```Python +{!> ../../../docs_src/app_testing/app_b/test_main.py!} +``` + +🕐❔ 👆 💪 👩‍💻 🚶‍♀️ ℹ 📨 & 👆 🚫 💭 ❔, 👆 💪 🔎 (🇺🇸🔍) ❔ ⚫️ `httpx`, ⚖️ ❔ ⚫️ ⏮️ `requests`, 🇸🇲 🔧 ⚓️ 🔛 📨' 🔧. + +⤴️ 👆 🎏 👆 💯. + +🤶 Ⓜ.: + +* 🚶‍♀️ *➡* ⚖️ *🔢* 🔢, 🚮 ⚫️ 📛 ⚫️. +* 🚶‍♀️ 🎻 💪, 🚶‍♀️ 🐍 🎚 (✅ `dict`) 🔢 `json`. +* 🚥 👆 💪 📨 *📨 💽* ↩️ 🎻, ⚙️ `data` 🔢 ↩️. +* 🚶‍♀️ *🎚*, ⚙️ `dict` `headers` 🔢. +* *🍪*, `dict` `cookies` 🔢. + +🌖 ℹ 🔃 ❔ 🚶‍♀️ 💽 👩‍💻 (⚙️ `httpx` ⚖️ `TestClient`) ✅ 🇸🇲 🧾. + +!!! info + 🗒 👈 `TestClient` 📨 💽 👈 💪 🗜 🎻, 🚫 Pydantic 🏷. + + 🚥 👆 ✔️ Pydantic 🏷 👆 💯 & 👆 💚 📨 🚮 💽 🈸 ⏮️ 🔬, 👆 💪 ⚙️ `jsonable_encoder` 🔬 [🎻 🔗 🔢](encoder.md){.internal-link target=_blank}. + +## 🏃 ⚫️ + +⏮️ 👈, 👆 💪 ❎ `pytest`: + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +⚫️ 🔜 🔍 📁 & 💯 🔁, 🛠️ 👫, & 📄 🏁 🔙 👆. + +🏃 💯 ⏮️: + +
+ +```console +$ pytest + +================ test session starts ================ +platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 +rootdir: /home/user/code/superawesome-cli/app +plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 +collected 6 items + +---> 100% + +test_main.py ...... [100%] + +================= 1 passed in 0.03s ================= +``` + +
diff --git a/docs/em/mkdocs.yml b/docs/em/mkdocs.yml new file mode 100644 index 0000000000..de18856f44 --- /dev/null +++ b/docs/em/mkdocs.yml @@ -0,0 +1 @@ +INHERIT: ../en/mkdocs.yml diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml index 4a5791a43c..ad738df353 100644 --- a/docs/en/data/external_links.yml +++ b/docs/en/data/external_links.yml @@ -1,5 +1,21 @@ articles: english: + - author: Raf Rasenberg + author_link: https://rafrasenberg.com/about/ + link: https://rafrasenberg.com/fastapi-lambda/ + title: 'FastAPI lambda container: serverless simplified' + - author: Teresa N. Fontanella De Santis + author_link: https://dev.to/ + link: https://dev.to/teresafds/authorization-on-fastapi-with-casbin-41og + title: Authorization on FastAPI with Casbin + - author: WayScript + author_link: https://www.wayscript.com + link: https://blog.wayscript.com/fast-api-quickstart/ + title: Quickstart Guide to Build and Host Responsive APIs with Fast API and WayScript + - author: New Relic + author_link: https://newrelic.com + link: https://newrelic.com/instant-observability/fastapi/e559ec64-f765-4470-a15f-1901fcebb468 + title: How to monitor FastAPI application performance using Python agent - author: Jean-Baptiste Rocher author_link: https://hashnode.com/@jibrocher link: https://dev.indooroutdoor.io/series/fastapi-react-poll-app @@ -212,7 +228,15 @@ articles: author_link: https://twitter.com/MantoshMukul link: https://www.jetbrains.com/pycharm/guide/tutorials/fastapi-aws-kubernetes/ title: Developing FastAPI Application using K8s & AWS + - author: KrishNa + author_link: https://medium.com/@krishnardt365 + link: https://medium.com/@krishnardt365/fastapi-docker-and-postgres-91943e71be92 + title: Fastapi, Docker(Docker compose) and Postgres german: + - author: Marcel Sander (actidoo) + author_link: https://www.actidoo.com + link: https://www.actidoo.com/de/blog/python-fastapi-domain-driven-design + title: Domain-driven Design mit Python und FastAPI - author: Nico Axtmann author_link: https://twitter.com/_nicoax link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/ @@ -288,6 +312,11 @@ articles: author_link: https://fullstackstation.com/author/figonking/ link: https://fullstackstation.com/fastapi-trien-khai-bang-docker/ title: 'FASTAPI: TRIỂN KHAI BẰNG DOCKER' + taiwanese: + - author: Leon + author_link: http://editor.leonh.space/ + link: https://editor.leonh.space/2022/tortoise/ + title: 'Tortoise ORM / FastAPI 整合快速筆記' podcasts: english: - author: Podcast.`__init__` diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml index db4a9acc6a..71afb66b14 100644 --- a/docs/en/data/github_sponsors.yml +++ b/docs/en/data/github_sponsors.yml @@ -2,111 +2,108 @@ sponsors: - - login: cryptapi avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4 url: https://github.com/cryptapi - - login: jina-ai - avatarUrl: https://avatars.githubusercontent.com/u/60539444?v=4 - url: https://github.com/jina-ai - - login: DropbaseHQ - avatarUrl: https://avatars.githubusercontent.com/u/85367855?v=4 - url: https://github.com/DropbaseHQ -- - login: sushi2all - avatarUrl: https://avatars.githubusercontent.com/u/1043732?v=4 - url: https://github.com/sushi2all - - login: chaserowbotham - avatarUrl: https://avatars.githubusercontent.com/u/97751084?v=4 - url: https://github.com/chaserowbotham +- - login: nihpo + avatarUrl: https://avatars.githubusercontent.com/u/1841030?u=0264956d7580f7e46687a762a7baa629f84cf97c&v=4 + url: https://github.com/nihpo + - login: ObliviousAI + avatarUrl: https://avatars.githubusercontent.com/u/65656077?v=4 + url: https://github.com/ObliviousAI - - login: mikeckennedy avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=1bb18268bcd4d9249e1f783a063c27df9a84c05b&v=4 url: https://github.com/mikeckennedy - - login: Trivie - avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4 - url: https://github.com/Trivie - login: deta avatarUrl: https://avatars.githubusercontent.com/u/47275976?v=4 url: https://github.com/deta - login: deepset-ai avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4 url: https://github.com/deepset-ai - - login: investsuite - avatarUrl: https://avatars.githubusercontent.com/u/73833632?v=4 - url: https://github.com/investsuite + - login: svix + avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4 + url: https://github.com/svix + - login: databento-bot + avatarUrl: https://avatars.githubusercontent.com/u/98378480?u=494f679996e39427f7ddb1a7de8441b7c96fb670&v=4 + url: https://github.com/databento-bot - login: VincentParedes avatarUrl: https://avatars.githubusercontent.com/u/103889729?v=4 url: https://github.com/VincentParedes -- - login: plocher - avatarUrl: https://avatars.githubusercontent.com/u/1082871?v=4 - url: https://github.com/plocher -- - login: InesIvanova - avatarUrl: https://avatars.githubusercontent.com/u/22920417?u=409882ec1df6dbd77455788bb383a8de223dbf6f&v=4 - url: https://github.com/InesIvanova -- - login: SendCloud - avatarUrl: https://avatars.githubusercontent.com/u/7831959?v=4 - url: https://github.com/SendCloud - - login: qaas - avatarUrl: https://avatars.githubusercontent.com/u/8503759?u=10a6b4391ad6ab4cf9487ce54e3fcb61322d1efc&v=4 - url: https://github.com/qaas +- - login: getsentry + avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4 + url: https://github.com/getsentry +- - login: takashi-yoneya + avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4 + url: https://github.com/takashi-yoneya + - login: mercedes-benz + avatarUrl: https://avatars.githubusercontent.com/u/34240465?v=4 + url: https://github.com/mercedes-benz - login: xoflare avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4 url: https://github.com/xoflare - - login: Striveworks - avatarUrl: https://avatars.githubusercontent.com/u/45523576?v=4 - url: https://github.com/Striveworks + - login: marvin-robot + avatarUrl: https://avatars.githubusercontent.com/u/41086007?u=091c5cb75af363123d66f58194805a97220ee1a7&v=4 + url: https://github.com/marvin-robot - login: BoostryJP avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4 url: https://github.com/BoostryJP -- - login: johnadjei - avatarUrl: https://avatars.githubusercontent.com/u/767860?v=4 - url: https://github.com/johnadjei - - login: HiredScore +- - login: HiredScore avatarUrl: https://avatars.githubusercontent.com/u/3908850?v=4 url: https://github.com/HiredScore - - login: wdwinslow - avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4 - url: https://github.com/wdwinslow + - login: Trivie + avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4 + url: https://github.com/Trivie +- - login: JonasKs + avatarUrl: https://avatars.githubusercontent.com/u/5310116?u=98a049f3e1491bffb91e1feb7e93def6881a9389&v=4 + url: https://github.com/JonasKs - - login: moellenbeck avatarUrl: https://avatars.githubusercontent.com/u/169372?v=4 url: https://github.com/moellenbeck + - login: birkjernstrom + avatarUrl: https://avatars.githubusercontent.com/u/281715?u=4be14b43f76b4bd497b1941309bb390250b405e6&v=4 + url: https://github.com/birkjernstrom + - login: AccentDesign + avatarUrl: https://avatars.githubusercontent.com/u/2429332?v=4 + url: https://github.com/AccentDesign - login: RodneyU215 avatarUrl: https://avatars.githubusercontent.com/u/3329665?u=ec6a9adf8e7e8e306eed7d49687c398608d1604f&v=4 url: https://github.com/RodneyU215 - - login: grillazz - avatarUrl: https://avatars.githubusercontent.com/u/3415861?u=0b32b7073ae1ab8b7f6d2db0188c2e1e357ff451&v=4 - url: https://github.com/grillazz - login: tizz98 avatarUrl: https://avatars.githubusercontent.com/u/5739698?u=f095a3659e3a8e7c69ccd822696990b521ea25f9&v=4 url: https://github.com/tizz98 - - login: jmaralc - avatarUrl: https://avatars.githubusercontent.com/u/21101214?u=b15a9f07b7cbf6c9dcdbcb6550bbd2c52f55aa50&v=4 - url: https://github.com/jmaralc - - login: marutoraman - avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4 - url: https://github.com/marutoraman + - login: americanair + avatarUrl: https://avatars.githubusercontent.com/u/12281813?v=4 + url: https://github.com/americanair - login: mainframeindustries avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4 url: https://github.com/mainframeindustries - - login: A-Edge - avatarUrl: https://avatars.githubusercontent.com/u/59514131?v=4 - url: https://github.com/A-Edge +- - login: povilasb + avatarUrl: https://avatars.githubusercontent.com/u/1213442?u=b11f58ed6ceea6e8297c9b310030478ebdac894d&v=4 + url: https://github.com/povilasb + - login: primer-io + avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4 + url: https://github.com/primer-io +- - login: indeedeng + avatarUrl: https://avatars.githubusercontent.com/u/2905043?v=4 + url: https://github.com/indeedeng - - login: Kludex avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex - login: samuelcolvin avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4 url: https://github.com/samuelcolvin - - login: jokull - avatarUrl: https://avatars.githubusercontent.com/u/701?u=0532b62166893d5160ef795c4c8b7512d971af05&v=4 - url: https://github.com/jokull - login: jefftriplett avatarUrl: https://avatars.githubusercontent.com/u/50527?u=af1ddfd50f6afd6d99f333ba2ac8d0a5b245ea74&v=4 url: https://github.com/jefftriplett + - login: medecau + avatarUrl: https://avatars.githubusercontent.com/u/59870?u=f9341c95adaba780828162fd4c7442357ecfcefa&v=4 + url: https://github.com/medecau - login: kamalgill avatarUrl: https://avatars.githubusercontent.com/u/133923?u=0df9181d97436ce330e9acf90ab8a54b7022efe7&v=4 url: https://github.com/kamalgill - - login: jsutton - avatarUrl: https://avatars.githubusercontent.com/u/280777?v=4 - url: https://github.com/jsutton - - login: deserat - avatarUrl: https://avatars.githubusercontent.com/u/299332?v=4 - url: https://github.com/deserat + - login: dekoza + avatarUrl: https://avatars.githubusercontent.com/u/210980?u=c03c78a8ae1039b500dfe343665536ebc51979b2&v=4 + url: https://github.com/dekoza + - login: pamelafox + avatarUrl: https://avatars.githubusercontent.com/u/297042?v=4 + url: https://github.com/pamelafox - login: ericof avatarUrl: https://avatars.githubusercontent.com/u/306014?u=cf7c8733620397e6584a451505581c01c5d842d7&v=4 url: https://github.com/ericof @@ -116,27 +113,27 @@ sponsors: - login: koxudaxi avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4 url: https://github.com/koxudaxi + - login: falkben + avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 + url: https://github.com/falkben - login: jqueguiner - avatarUrl: https://avatars.githubusercontent.com/u/690878?u=e4835b2a985a0f2d52018e4926cb5a58c26a62e8&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/690878?u=bd65cc1f228ce6455e56dfaca3ef47c33bc7c3b0&v=4 url: https://github.com/jqueguiner - - login: ltieman - avatarUrl: https://avatars.githubusercontent.com/u/1084689?u=e69b17de17cb3ca141a17daa7ccbe173ceb1eb17&v=4 - url: https://github.com/ltieman - - login: westonsteimel - avatarUrl: https://avatars.githubusercontent.com/u/1593939?u=0f2c0e3647f916fe295d62fa70da7a4c177115e3&v=4 - url: https://github.com/westonsteimel - - login: corleyma - avatarUrl: https://avatars.githubusercontent.com/u/2080732?u=aed2ff652294a87d666b1c3f6dbe98104db76d26&v=4 - url: https://github.com/corleyma - - login: madisonmay - avatarUrl: https://avatars.githubusercontent.com/u/2645393?u=f22b93c6ea345a4d26a90a3834dfc7f0789fcb63&v=4 - url: https://github.com/madisonmay - - login: saivarunk - avatarUrl: https://avatars.githubusercontent.com/u/2976867?u=71f4385e781e9a9e871a52f2d4686f9a8d69ba2f&v=4 - url: https://github.com/saivarunk - - login: andre1sk - avatarUrl: https://avatars.githubusercontent.com/u/3148093?v=4 - url: https://github.com/andre1sk + - login: iobruno + avatarUrl: https://avatars.githubusercontent.com/u/901651?u=460bc34ac298dca9870aafe3a1560a2ae789bc4a&v=4 + url: https://github.com/iobruno + - login: tcsmith + avatarUrl: https://avatars.githubusercontent.com/u/989034?u=7d8d741552b3279e8f4d3878679823a705a46f8f&v=4 + url: https://github.com/tcsmith + - login: mrkmcknz + avatarUrl: https://avatars.githubusercontent.com/u/1089376?u=2b9b8a8c25c33a4f6c220095638bd821cdfd13a3&v=4 + url: https://github.com/mrkmcknz + - login: mickaelandrieu + avatarUrl: https://avatars.githubusercontent.com/u/1247388?u=599f6e73e452a9453f2bd91e5c3100750e731ad4&v=4 + url: https://github.com/mickaelandrieu + - login: jonakoudijs + avatarUrl: https://avatars.githubusercontent.com/u/1906344?u=5ca0c9a1a89b6a2ba31abe35c66bdc07af60a632&v=4 + url: https://github.com/jonakoudijs - login: Shark009 avatarUrl: https://avatars.githubusercontent.com/u/3163309?u=0c6f4091b0eda05c44c390466199826e6dc6e431&v=4 url: https://github.com/Shark009 @@ -146,45 +143,39 @@ sponsors: - login: zsinx6 avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4 url: https://github.com/zsinx6 + - login: aacayaco + avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4 + url: https://github.com/aacayaco - login: anomaly avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4 url: https://github.com/anomaly - - login: peterHoburg - avatarUrl: https://avatars.githubusercontent.com/u/3860655?u=f55f47eb2d6a9b495e806ac5a044e3ae01ccc1fa&v=4 - url: https://github.com/peterHoburg + - login: jgreys + avatarUrl: https://avatars.githubusercontent.com/u/4136890?u=c66ae617d614f6c886f1f1c1799d22100b3c848d&v=4 + url: https://github.com/jgreys - login: jaredtrog avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4 url: https://github.com/jaredtrog - login: oliverxchen avatarUrl: https://avatars.githubusercontent.com/u/4471774?u=534191f25e32eeaadda22dfab4b0a428733d5489&v=4 url: https://github.com/oliverxchen - - login: CINOAdam - avatarUrl: https://avatars.githubusercontent.com/u/4728508?u=76ef23f06ae7c604e009873bc27cf0ea9ba738c9&v=4 - url: https://github.com/CINOAdam - - login: ScrimForever - avatarUrl: https://avatars.githubusercontent.com/u/5040124?u=091ec38bfe16d6e762099e91309b59f248616a65&v=4 - url: https://github.com/ScrimForever - login: ennui93 avatarUrl: https://avatars.githubusercontent.com/u/5300907?u=5b5452725ddb391b2caaebf34e05aba873591c3a&v=4 url: https://github.com/ennui93 - - login: MacroPower - avatarUrl: https://avatars.githubusercontent.com/u/5648814?u=e13991efd1e03c44c911f919872e750530ded633&v=4 - url: https://github.com/MacroPower + - login: ternaus + avatarUrl: https://avatars.githubusercontent.com/u/5481618?u=fabc8d75c921b3380126adb5a931c5da6e7db04f&v=4 + url: https://github.com/ternaus + - login: eseglem + avatarUrl: https://avatars.githubusercontent.com/u/5920492?u=208d419cf667b8ac594c82a8db01932c7e50d057&v=4 + url: https://github.com/eseglem - login: Yaleesa avatarUrl: https://avatars.githubusercontent.com/u/6135475?v=4 url: https://github.com/Yaleesa - login: iwpnd - avatarUrl: https://avatars.githubusercontent.com/u/6152183?u=b2286006daafff5f991557344fee20b5da59639a&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/6152183?u=c485eefca5c6329600cae63dd35e4f5682ce6924&v=4 url: https://github.com/iwpnd - login: simw avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4 url: https://github.com/simw - - login: pkucmus - avatarUrl: https://avatars.githubusercontent.com/u/6347418?u=98f5918b32e214a168a2f5d59b0b8ebdf57dca0d&v=4 - url: https://github.com/pkucmus - - login: s3ich4n - avatarUrl: https://avatars.githubusercontent.com/u/6926298?u=ba3025d698e1c986655e776ae383a3d60d9d578e&v=4 - url: https://github.com/s3ich4n - login: Rehket avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4 url: https://github.com/Rehket @@ -194,138 +185,120 @@ sponsors: - login: Shackelford-Arden avatarUrl: https://avatars.githubusercontent.com/u/7362263?v=4 url: https://github.com/Shackelford-Arden - - login: Vikka - avatarUrl: https://avatars.githubusercontent.com/u/9381120?u=4bfc7032a824d1ed1994aa8256dfa597c8f187ad&v=4 - url: https://github.com/Vikka - - login: Ge0f3 - avatarUrl: https://avatars.githubusercontent.com/u/11887760?u=ccd80f1ac36dcb8517ef5c4e702e8cc5a80cad2f&v=4 - url: https://github.com/Ge0f3 - - login: gokulyc - avatarUrl: https://avatars.githubusercontent.com/u/13468848?u=269f269d3e70407b5fb80138c52daba7af783997&v=4 - url: https://github.com/gokulyc + - login: savannahostrowski + avatarUrl: https://avatars.githubusercontent.com/u/8949415?u=c3177aa099fb2b8c36aeba349278b77f9a8df211&v=4 + url: https://github.com/savannahostrowski + - login: wdwinslow + avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4 + url: https://github.com/wdwinslow - login: dannywade avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4 url: https://github.com/dannywade - - login: pablonnaoji - avatarUrl: https://avatars.githubusercontent.com/u/15187159?u=afc15bd5a4ba9c5c7206bbb1bcaeef606a0932e0&v=4 - url: https://github.com/pablonnaoji - - login: robintully - avatarUrl: https://avatars.githubusercontent.com/u/17059673?u=862b9bb01513f5acd30df97433cb97a24dbfb772&v=4 - url: https://github.com/robintully + - login: khadrawy + avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4 + url: https://github.com/khadrawy + - login: mjohnsey + avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4 + url: https://github.com/mjohnsey - login: wedwardbeck avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4 url: https://github.com/wedwardbeck - - login: linusg - avatarUrl: https://avatars.githubusercontent.com/u/19366641?u=125e390abef8fff3b3b0d370c369cba5d7fd4c67&v=4 - url: https://github.com/linusg - - login: stradivari96 - avatarUrl: https://avatars.githubusercontent.com/u/19752586?u=255f5f06a768f518b20cebd6963e840ac49294fd&v=4 - url: https://github.com/stradivari96 - - login: RedCarpetUp - avatarUrl: https://avatars.githubusercontent.com/u/20360440?v=4 - url: https://github.com/RedCarpetUp + - login: RaamEEIL + avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4 + url: https://github.com/RaamEEIL - login: Filimoa - avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=75e02d102d2ee3e3d793e555fa5c63045913ccb0&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=0be845711495bbd7b756e13fcaeb8efc1ebd78ba&v=4 url: https://github.com/Filimoa - login: shuheng-liu avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4 url: https://github.com/shuheng-liu - - login: cometa-haley - avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4 - url: https://github.com/cometa-haley + - login: SebTota + avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4 + url: https://github.com/SebTota - login: LarryGF avatarUrl: https://avatars.githubusercontent.com/u/26148349?u=431bb34d36d41c172466252242175281ae132152&v=4 url: https://github.com/LarryGF - - login: veprimk - avatarUrl: https://avatars.githubusercontent.com/u/29689749?u=f8cb5a15a286e522e5b189bc572d5a1a90217fb2&v=4 - url: https://github.com/veprimk - - login: meysam81 - avatarUrl: https://avatars.githubusercontent.com/u/30233243?u=64dc9fc62d039892c6fb44d804251cad5537132b&v=4 - url: https://github.com/meysam81 + - login: BrettskiPy + avatarUrl: https://avatars.githubusercontent.com/u/30988215?u=d8a94a67e140d5ee5427724b292cc52d8827087a&v=4 + url: https://github.com/BrettskiPy - login: mauroalejandrojm avatarUrl: https://avatars.githubusercontent.com/u/31569442?u=cdada990a1527926a36e95f62c30a8b48bbc49a1&v=4 url: https://github.com/mauroalejandrojm - login: Leay15 avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4 url: https://github.com/Leay15 + - login: ygorpontelo + avatarUrl: https://avatars.githubusercontent.com/u/32963605?u=35f7103f9c4c4c2589ae5737ee882e9375ef072e&v=4 + url: https://github.com/ygorpontelo - login: AlrasheedA avatarUrl: https://avatars.githubusercontent.com/u/33544979?u=7fe66bf62b47682612b222e3e8f4795ef3be769b&v=4 url: https://github.com/AlrasheedA - login: ProteinQure avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4 url: https://github.com/ProteinQure - - login: guligon90 - avatarUrl: https://avatars.githubusercontent.com/u/35070513?u=b48c05f669d1ea1d329f90dc70e45f10b569ef55&v=4 - url: https://github.com/guligon90 + - login: askurihin + avatarUrl: https://avatars.githubusercontent.com/u/37978981?v=4 + url: https://github.com/askurihin + - login: arleybri18 + avatarUrl: https://avatars.githubusercontent.com/u/39681546?u=5c028f81324b0e8c73b3c15bc4e7b0218d2ba0c3&v=4 + url: https://github.com/arleybri18 + - login: thenickben + avatarUrl: https://avatars.githubusercontent.com/u/40610922?u=1e907d904041b7c91213951a3cb344cd37c14aaf&v=4 + url: https://github.com/thenickben - login: ybressler - avatarUrl: https://avatars.githubusercontent.com/u/40807730?u=6621dc9ab53b697912ab2a32211bb29ae90a9112&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/40807730?u=41e2c00f1eebe3c402635f0325e41b4e6511462c&v=4 url: https://github.com/ybressler - - login: iamkarshe - avatarUrl: https://avatars.githubusercontent.com/u/43641892?u=d08c901b359c931784501740610d416558ff3e24&v=4 - url: https://github.com/iamkarshe - - login: dbanty - avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4 - url: https://github.com/dbanty - - login: rafsaf - avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4 - url: https://github.com/rafsaf + - login: ddilidili + avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4 + url: https://github.com/ddilidili - login: dudikbender - avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=494f85229115076121b3639a3806bbac1c6ae7f6&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=3a57542938ebfd57579a0111db2b297e606d9681&v=4 url: https://github.com/dudikbender - - login: daisuke8000 - avatarUrl: https://avatars.githubusercontent.com/u/55035595?u=5025e379cd3655ae1a96039efc85223a873d2e38&v=4 - url: https://github.com/daisuke8000 + - login: thisistheplace + avatarUrl: https://avatars.githubusercontent.com/u/57633545?u=a3f3a7f8ace8511c6c067753f6eb6aee0db11ac6&v=4 + url: https://github.com/thisistheplace + - login: A-Edge + avatarUrl: https://avatars.githubusercontent.com/u/59514131?v=4 + url: https://github.com/A-Edge - login: yakkonaut avatarUrl: https://avatars.githubusercontent.com/u/60633704?u=90a71fd631aa998ba4a96480788f017c9904e07b&v=4 url: https://github.com/yakkonaut - - login: primer-io - avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4 - url: https://github.com/primer-io - - login: around - avatarUrl: https://avatars.githubusercontent.com/u/62425723?v=4 - url: https://github.com/around - - login: predictionmachine - avatarUrl: https://avatars.githubusercontent.com/u/63719559?v=4 - url: https://github.com/predictionmachine + - login: patsatsia + avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4 + url: https://github.com/patsatsia - login: daverin avatarUrl: https://avatars.githubusercontent.com/u/70378377?u=6d1814195c0de7162820eaad95a25b423a3869c0&v=4 url: https://github.com/daverin - login: anthonycepeda - avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=4252c6b6dc5024af502a823a3ac5e7a03a69963f&v=4 url: https://github.com/anthonycepeda - - login: NinaHwang - avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4 - url: https://github.com/NinaHwang - - login: dotlas - avatarUrl: https://avatars.githubusercontent.com/u/88832003?v=4 - url: https://github.com/dotlas + - login: DelfinaCare + avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4 + url: https://github.com/DelfinaCare + - login: osawa-koki + avatarUrl: https://avatars.githubusercontent.com/u/94336223?u=59c6fe6945bcbbaff87b2a794238671b060620d2&v=4 + url: https://github.com/osawa-koki - login: pyt3h avatarUrl: https://avatars.githubusercontent.com/u/99658549?v=4 url: https://github.com/pyt3h -- - login: linux-china - avatarUrl: https://avatars.githubusercontent.com/u/46711?v=4 - url: https://github.com/linux-china + - login: Dagmaara + avatarUrl: https://avatars.githubusercontent.com/u/115501964?v=4 + url: https://github.com/Dagmaara +- - login: Yarden-zamir + avatarUrl: https://avatars.githubusercontent.com/u/8178413?u=ee177a8b0f87ea56747f4d96f34cd4e9604a8217&v=4 + url: https://github.com/Yarden-zamir +- - login: pawamoy + avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4 + url: https://github.com/pawamoy - login: ddanier - avatarUrl: https://avatars.githubusercontent.com/u/113563?v=4 + avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4 url: https://github.com/ddanier - - login: jhb - avatarUrl: https://avatars.githubusercontent.com/u/142217?v=4 - url: https://github.com/jhb - - login: justinrmiller - avatarUrl: https://avatars.githubusercontent.com/u/143998?u=b507a940394d4fc2bc1c27cea2ca9c22538874bd&v=4 - url: https://github.com/justinrmiller - login: bryanculbertson avatarUrl: https://avatars.githubusercontent.com/u/144028?u=defda4f90e93429221cc667500944abde60ebe4a&v=4 url: https://github.com/bryanculbertson - - login: yourkin - avatarUrl: https://avatars.githubusercontent.com/u/178984?u=fa7c3503b47bf16405b96d21554bc59f07a65523&v=4 - url: https://github.com/yourkin - login: slafs avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 url: https://github.com/slafs - - login: assem-ch - avatarUrl: https://avatars.githubusercontent.com/u/315228?u=e0c5ab30726d3243a40974bb9bae327866e42d9b&v=4 - url: https://github.com/assem-ch - login: adamghill avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4 url: https://github.com/adamghill @@ -335,75 +308,78 @@ sponsors: - login: dmig avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4 url: https://github.com/dmig - - login: rinckd - avatarUrl: https://avatars.githubusercontent.com/u/546002?u=1fcc7e664dc86524a0af6837a0c222829c3fd4e5&v=4 - url: https://github.com/rinckd - login: securancy avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4 url: https://github.com/securancy - - login: falkben - avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4 - url: https://github.com/falkben - login: hardbyte avatarUrl: https://avatars.githubusercontent.com/u/855189?u=aa29e92f34708814d6b67fcd47ca4cf2ce1c04ed&v=4 url: https://github.com/hardbyte + - login: browniebroke + avatarUrl: https://avatars.githubusercontent.com/u/861044?u=5abfca5588f3e906b31583d7ee62f6de4b68aa24&v=4 + url: https://github.com/browniebroke - login: janfilips - avatarUrl: https://avatars.githubusercontent.com/u/870699?u=6034d81731ecb41ae5c717e56a901ed46fc039a8&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/870699?u=96df18ad355e58b9397accc55f4eeb7a86e959b0&v=4 url: https://github.com/janfilips - - login: scari - avatarUrl: https://avatars.githubusercontent.com/u/964251?v=4 - url: https://github.com/scari - - login: Pytlicek - avatarUrl: https://avatars.githubusercontent.com/u/1430522?u=169dba3bfbc04ed214a914640ff435969f19ddb3&v=4 - url: https://github.com/Pytlicek - - login: Celeborn2BeAlive - avatarUrl: https://avatars.githubusercontent.com/u/1659465?u=944517e4db0f6df65070074e81cabdad9c8a434b&v=4 - url: https://github.com/Celeborn2BeAlive - login: WillHogan avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4 url: https://github.com/WillHogan + - login: my3 + avatarUrl: https://avatars.githubusercontent.com/u/1825270?v=4 + url: https://github.com/my3 - login: cbonoz avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4 url: https://github.com/cbonoz - - login: Abbe98 - avatarUrl: https://avatars.githubusercontent.com/u/2631719?u=8a064aba9a710229ad28c616549d81a24191a5df&v=4 - url: https://github.com/Abbe98 - - login: rglsk - avatarUrl: https://avatars.githubusercontent.com/u/2768101?u=e349c88673f2155fe021331377c656a9d74bcc25&v=4 - url: https://github.com/rglsk - - login: paul121 - avatarUrl: https://avatars.githubusercontent.com/u/3116995?u=6e2d8691cc345e63ee02e4eb4d7cef82b1fcbedc&v=4 - url: https://github.com/paul121 - - login: igorcorrea - avatarUrl: https://avatars.githubusercontent.com/u/3438238?u=c57605077c31a8f7b2341fc4912507f91b4a5621&v=4 - url: https://github.com/igorcorrea + - login: Patechoc + avatarUrl: https://avatars.githubusercontent.com/u/2376641?u=23b49e9eda04f078cb74fa3f93593aa6a57bb138&v=4 + url: https://github.com/Patechoc + - login: larsvik + avatarUrl: https://avatars.githubusercontent.com/u/3442226?v=4 + url: https://github.com/larsvik - login: anthonycorletti avatarUrl: https://avatars.githubusercontent.com/u/3477132?v=4 url: https://github.com/anthonycorletti - - login: pawamoy - avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4 - url: https://github.com/pawamoy + - login: jonathanhle + avatarUrl: https://avatars.githubusercontent.com/u/3851599?u=76b9c5d2fecd6c3a16e7645231878c4507380d4d&v=4 + url: https://github.com/jonathanhle + - login: nikeee + avatarUrl: https://avatars.githubusercontent.com/u/4068864?u=bbe73151f2b409c120160d032dc9aa6875ef0c4b&v=4 + url: https://github.com/nikeee - login: Alisa-lisa avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4 url: https://github.com/Alisa-lisa + - login: danielunderwood + avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4 + url: https://github.com/danielunderwood + - login: yuawn + avatarUrl: https://avatars.githubusercontent.com/u/5111198?u=5315576f3fe1a70fd2d0f02181588f4eea5d353d&v=4 + url: https://github.com/yuawn + - login: sdevkota + avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4 + url: https://github.com/sdevkota - login: unredundant - avatarUrl: https://avatars.githubusercontent.com/u/5607577?u=57dd0023365bec03f4fc566df6b81bc0a264a47d&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/5607577?u=1ffbf39f5bb8736b75c0d235707d6e8f803725c5&v=4 url: https://github.com/unredundant - - login: holec - avatarUrl: https://avatars.githubusercontent.com/u/6438041?u=f5af71ec85b3a9d7b8139cb5af0512b02fa9ab1e&v=4 - url: https://github.com/holec + - login: Baghdady92 + avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4 + url: https://github.com/Baghdady92 + - login: KentShikama + avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4 + url: https://github.com/KentShikama + - login: katnoria + avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4 + url: https://github.com/katnoria + - login: mattwelke + avatarUrl: https://avatars.githubusercontent.com/u/7719209?u=80f02a799323b1472b389b836d95957c93a6d856&v=4 + url: https://github.com/mattwelke + - login: hcristea + avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4 + url: https://github.com/hcristea - login: moonape1226 avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4 url: https://github.com/moonape1226 - - login: davanstrien - avatarUrl: https://avatars.githubusercontent.com/u/8995957?u=fb2aad2b52bb4e7b56db6d7c8ecc9ae1eac1b984&v=4 - url: https://github.com/davanstrien - - login: yenchenLiu - avatarUrl: https://avatars.githubusercontent.com/u/9199638?u=8cdf5ae507448430d90f6f3518d1665a23afe99b&v=4 - url: https://github.com/yenchenLiu - - login: VivianSolide - avatarUrl: https://avatars.githubusercontent.com/u/9358572?u=4a38ef72dd39e8b262bd5ab819992128b55c52b4&v=4 - url: https://github.com/VivianSolide + - login: albertkun + avatarUrl: https://avatars.githubusercontent.com/u/8574425?u=aad2a9674273c9275fe414d99269b7418d144089&v=4 + url: https://github.com/albertkun - login: xncbf avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=866a1311e4bd3ec5ae84185c4fcc99f397c883d7&v=4 url: https://github.com/xncbf @@ -413,84 +389,108 @@ sponsors: - login: hard-coders avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 url: https://github.com/hard-coders + - login: supdann + avatarUrl: https://avatars.githubusercontent.com/u/9986994?u=9671810f4ae9504c063227fee34fd47567ff6954&v=4 + url: https://github.com/supdann - login: satwikkansal avatarUrl: https://avatars.githubusercontent.com/u/10217535?u=b12d6ef74ea297de9e46da6933b1a5b7ba9e6a61&v=4 url: https://github.com/satwikkansal + - login: mntolia + avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4 + url: https://github.com/mntolia - login: pheanex avatarUrl: https://avatars.githubusercontent.com/u/10408624?u=5b6bab6ee174aa6e991333e06eb29f628741013d&v=4 url: https://github.com/pheanex - login: JimFawkes avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4 url: https://github.com/JimFawkes - - login: logan-connolly - avatarUrl: https://avatars.githubusercontent.com/u/16244943?u=8ae66dfbba936463cc8aa0dd7a6d2b4c0cc757eb&v=4 - url: https://github.com/logan-connolly - - login: cdsre - avatarUrl: https://avatars.githubusercontent.com/u/16945936?v=4 - url: https://github.com/cdsre + - login: giuliano-oliveira + avatarUrl: https://avatars.githubusercontent.com/u/13181797?u=0ef2dfbf7fc9a9726d45c21d32b5d1038a174870&v=4 + url: https://github.com/giuliano-oliveira + - login: TheR1D + avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4 + url: https://github.com/TheR1D - login: jangia avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4 url: https://github.com/jangia - login: ghandic avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 url: https://github.com/ghandic - - login: fstau - avatarUrl: https://avatars.githubusercontent.com/u/24669867?u=60e7c8c09f8dafabee8fc3edcd6f9e19abbff918&v=4 - url: https://github.com/fstau + - login: pers0n4 + avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=f211a13a7b572cbbd7779b9c8d8cb428cc7ba07e&v=4 + url: https://github.com/pers0n4 + - login: kadekillary + avatarUrl: https://avatars.githubusercontent.com/u/25046261?u=e185e58080090f9e678192cd214a14b14a2b232b&v=4 + url: https://github.com/kadekillary + - login: hoenie-ams + avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4 + url: https://github.com/hoenie-ams + - login: joerambo + avatarUrl: https://avatars.githubusercontent.com/u/26282974?v=4 + url: https://github.com/joerambo + - login: rlnchow + avatarUrl: https://avatars.githubusercontent.com/u/28018479?u=a93ca9cf1422b9ece155784a72d5f2fdbce7adff&v=4 + url: https://github.com/rlnchow - login: mertguvencli avatarUrl: https://avatars.githubusercontent.com/u/29762151?u=16a906d90df96c8cff9ea131a575c4bc171b1523&v=4 url: https://github.com/mertguvencli - - login: dwreeves - avatarUrl: https://avatars.githubusercontent.com/u/31971762?u=69732aba05aa5cf0780866349ebe109cf632b047&v=4 - url: https://github.com/dwreeves - - login: kitaramu0401 - avatarUrl: https://avatars.githubusercontent.com/u/33246506?u=929e6efa2c518033b8097ba524eb5347a069bb3b&v=4 - url: https://github.com/kitaramu0401 + - login: HosamAlmoghraby + avatarUrl: https://avatars.githubusercontent.com/u/32025281?u=aa1b09feabccbf9dc506b81c71155f32d126cefa&v=4 + url: https://github.com/HosamAlmoghraby - login: engineerjoe440 avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4 url: https://github.com/engineerjoe440 + - login: bnkc + avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=9fbf76b9bf7786275e2900efa51d1394bcf1f06a&v=4 + url: https://github.com/bnkc - login: declon avatarUrl: https://avatars.githubusercontent.com/u/36180226?v=4 url: https://github.com/declon - - login: d-e-h-i-o - avatarUrl: https://avatars.githubusercontent.com/u/36816716?v=4 - url: https://github.com/d-e-h-i-o - - login: ilias-ant - avatarUrl: https://avatars.githubusercontent.com/u/42189572?u=a2d6121bac4d125d92ec207460fa3f1842d37e66&v=4 - url: https://github.com/ilias-ant + - login: miraedbswo + avatarUrl: https://avatars.githubusercontent.com/u/36796047?u=9e7a5b3e558edc61d35d0f9dfac37541bae7f56d&v=4 + url: https://github.com/miraedbswo + - login: kristiangronberg + avatarUrl: https://avatars.githubusercontent.com/u/42678548?v=4 + url: https://github.com/kristiangronberg - login: arrrrrmin - avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=fee5739394fea074cb0b66929d070114a5067aae&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=36a3880a6eb29309c19e6cadbb173bafbe91deb1&v=4 url: https://github.com/arrrrrmin - - login: Nephilim-Jack - avatarUrl: https://avatars.githubusercontent.com/u/48372168?u=6f2bb405238d7efc467536fe01f58df6779c58a9&v=4 - url: https://github.com/Nephilim-Jack - - login: akanz1 - avatarUrl: https://avatars.githubusercontent.com/u/51492342?u=2280f57134118714645e16b535c1a37adf6b369b&v=4 - url: https://github.com/akanz1 - - login: rooflexx - avatarUrl: https://avatars.githubusercontent.com/u/58993673?u=f8ba450460f1aea18430ed1e4a3889049a3b4dfa&v=4 - url: https://github.com/rooflexx - - login: denisyao1 - avatarUrl: https://avatars.githubusercontent.com/u/60019356?v=4 - url: https://github.com/denisyao1 - - login: apar-tiwari - avatarUrl: https://avatars.githubusercontent.com/u/61064197?v=4 - url: https://github.com/apar-tiwari + - login: ArtyomVancyan + avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4 + url: https://github.com/ArtyomVancyan + - login: hgalytoby + avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=f4888c2c54929bd86eed0d3971d09fcb306e5088&v=4 + url: https://github.com/hgalytoby + - login: eladgunders + avatarUrl: https://avatars.githubusercontent.com/u/52347338?u=83d454817cf991a035c8827d46ade050c813e2d6&v=4 + url: https://github.com/eladgunders + - login: conservative-dude + avatarUrl: https://avatars.githubusercontent.com/u/55538308?u=f250c44942ea6e73a6bd90739b381c470c192c11&v=4 + url: https://github.com/conservative-dude + - login: leo-jp-edwards + avatarUrl: https://avatars.githubusercontent.com/u/58213433?u=2c128e8b0794b7a66211cd7d8ebe05db20b7e9c0&v=4 + url: https://github.com/leo-jp-edwards + - login: tamtam-fitness + avatarUrl: https://avatars.githubusercontent.com/u/62091034?u=8da19a6bd3d02f5d6ba30c7247d5b46c98dd1403&v=4 + url: https://github.com/tamtam-fitness - login: 0417taehyun avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4 url: https://github.com/0417taehyun - - login: alessio-proietti - avatarUrl: https://avatars.githubusercontent.com/u/67370599?u=8ac73db1e18e946a7681f173abdb640516f88515&v=4 - url: https://github.com/alessio-proietti -- - login: backbord - avatarUrl: https://avatars.githubusercontent.com/u/6814946?v=4 - url: https://github.com/backbord +- - login: ssbarnea + avatarUrl: https://avatars.githubusercontent.com/u/102495?u=b4bf6818deefe59952ac22fec6ed8c76de1b8f7c&v=4 + url: https://github.com/ssbarnea - login: sadikkuzu - avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=765ed469c44c004560079210ccdad5b29938eaa9&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4 url: https://github.com/sadikkuzu - - login: gabrielmbmb - avatarUrl: https://avatars.githubusercontent.com/u/29572918?u=6d1e00b5d558e96718312ff910a2318f47cc3145&v=4 - url: https://github.com/gabrielmbmb + - login: ruizdiazever + avatarUrl: https://avatars.githubusercontent.com/u/29817086?u=2df54af55663d246e3a4dc8273711c37f1adb117&v=4 + url: https://github.com/ruizdiazever - login: danburonline avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=2cad4388c1544e539ecb732d656e42fb07b4ff2d&v=4 url: https://github.com/danburonline + - login: rwxd + avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4 + url: https://github.com/rwxd + - login: xNykram + avatarUrl: https://avatars.githubusercontent.com/u/55030025?u=2c1ba313fd79d29273b5ff7c9c5cf4edfb271b29&v=4 + url: https://github.com/xNykram diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 92aab109ff..2da1c968b2 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,223 +1,227 @@ maintainers: - login: tiangolo - answers: 1243 - prs: 300 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4 + answers: 1839 + prs: 398 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4 url: https://github.com/tiangolo experts: - login: Kludex - count: 335 + count: 410 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex - login: dmontagu - count: 262 + count: 237 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 url: https://github.com/dmontagu -- login: ycd - count: 221 - avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 - url: https://github.com/ycd - login: Mause - count: 207 + count: 220 avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 url: https://github.com/Mause +- login: ycd + count: 217 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4 + url: https://github.com/ycd +- login: JarroVGIT + count: 193 + avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 + url: https://github.com/JarroVGIT - login: euri10 - count: 166 + count: 152 avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 url: https://github.com/euri10 - login: phy25 - count: 130 + count: 126 avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 url: https://github.com/phy25 +- login: jgould22 + count: 124 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: iudeen + count: 118 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 + url: https://github.com/iudeen - login: raphaelauv - count: 71 + count: 83 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 url: https://github.com/raphaelauv +- login: ghandic + count: 71 + avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 + url: https://github.com/ghandic - login: ArcLightSlavik count: 71 avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 url: https://github.com/ArcLightSlavik - login: falkben - count: 58 - avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4 + count: 57 + avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 url: https://github.com/falkben - login: sm-Fifteen - count: 48 + count: 49 avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 url: https://github.com/sm-Fifteen +- login: yinziyan1206 + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + url: https://github.com/yinziyan1206 - login: insomnes - count: 46 + count: 45 avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 url: https://github.com/insomnes +- login: acidjunk + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 + url: https://github.com/acidjunk - login: Dustyposa - count: 43 + count: 45 avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 url: https://github.com/Dustyposa +- login: adriangb + count: 43 + avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=1e2c2c9b39f5c9b780fb933d8995cf08ec235a47&v=4 + url: https://github.com/adriangb +- login: frankie567 + count: 43 + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 + url: https://github.com/frankie567 +- login: odiseo0 + count: 42 + avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=2da05dab6cc8e1ade557801634760a56e4101796&v=4 + url: https://github.com/odiseo0 - login: includeamin - count: 39 + count: 40 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 url: https://github.com/includeamin -- login: jgould22 - count: 38 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 - login: STeveShary count: 37 avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 url: https://github.com/STeveShary -- login: adriangb - count: 36 - avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4 - url: https://github.com/adriangb -- login: prostomarkeloff - count: 33 - avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 - url: https://github.com/prostomarkeloff -- login: frankie567 - count: 31 - avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 - url: https://github.com/frankie567 - login: krishnardt - count: 31 + count: 35 avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 url: https://github.com/krishnardt -- login: wshayes - count: 29 - avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 - url: https://github.com/wshayes - login: chbndrhnns - count: 28 + count: 35 avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 url: https://github.com/chbndrhnns - login: panla - count: 26 + count: 32 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 url: https://github.com/panla -- login: ghandic - count: 25 - avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 - url: https://github.com/ghandic +- login: prostomarkeloff + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 + url: https://github.com/prostomarkeloff - login: dbanty - count: 25 + count: 26 avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4 url: https://github.com/dbanty +- login: wshayes + count: 25 + avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 + url: https://github.com/wshayes - login: SirTelemak - count: 24 + count: 23 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 url: https://github.com/SirTelemak - login: acnebs count: 22 avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4 url: https://github.com/acnebs +- login: rafsaf + count: 21 + avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=f8f0d6d6e90fac39fa786228158ba7f013c74271&v=4 + url: https://github.com/rafsaf - login: nsidnev - count: 22 + count: 20 avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4 url: https://github.com/nsidnev - login: chris-allnutt - count: 21 + count: 20 avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4 url: https://github.com/chris-allnutt +- login: zoliknemet + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/22326718?u=31ba446ac290e23e56eea8e4f0c558aaf0b40779&v=4 + url: https://github.com/zoliknemet - login: retnikt - count: 19 + count: 18 avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 url: https://github.com/retnikt -- login: acidjunk - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk - login: Hultner - count: 18 + count: 17 avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4 url: https://github.com/Hultner -- login: jorgerpo +- login: n8sty count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4 - url: https://github.com/jorgerpo -- login: nkhitrov - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4 - url: https://github.com/nkhitrov + avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 + url: https://github.com/n8sty - login: harunyasar count: 17 avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4 url: https://github.com/harunyasar -- login: rafsaf +- login: nkhitrov count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4 - url: https://github.com/rafsaf -- login: waynerv + avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4 + url: https://github.com/nkhitrov +- login: caeser1996 + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/16540232?u=05d2beb8e034d584d0a374b99d8826327bd7f614&v=4 + url: https://github.com/caeser1996 +- login: jonatasoli count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 - url: https://github.com/waynerv + avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4 + url: https://github.com/jonatasoli - login: dstlny count: 16 avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 url: https://github.com/dstlny -- login: haizaar - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4 - url: https://github.com/haizaar -- login: valentin994 - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4 - url: https://github.com/valentin994 +- login: ghost + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4 + url: https://github.com/ghost +- login: simondale00 + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/33907262?v=4 + url: https://github.com/simondale00 +- login: jorgerpo + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4 + url: https://github.com/jorgerpo +- login: ebottos94 + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/100039558?u=e2c672da5a7977fd24d87ce6ab35f8bf5b1ed9fa&v=4 + url: https://github.com/ebottos94 - login: hellocoldworld - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4 + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/47581948?u=3d2186796434c507a6cb6de35189ab0ad27c356f&v=4 url: https://github.com/hellocoldworld -- login: David-Lor - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4 - url: https://github.com/David-Lor -- login: yinziyan1206 - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/37829370?v=4 - url: https://github.com/yinziyan1206 -- login: jonatasoli - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4 - url: https://github.com/jonatasoli -- login: lowercase00 - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/21188280?v=4 - url: https://github.com/lowercase00 -- login: zamiramir - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/40475662?u=e58ef61034e8d0d6a312cc956fb09b9c3332b449&v=4 - url: https://github.com/zamiramir -- login: juntatalor - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4 - url: https://github.com/juntatalor -- login: n8sty - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty -- login: aalifadv - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/78442260?v=4 - url: https://github.com/aalifadv last_month_active: - login: jgould22 - count: 15 + count: 13 avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 url: https://github.com/jgould22 -- login: accelleon - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/5001614?v=4 - url: https://github.com/accelleon -- login: jonatasoli - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4 - url: https://github.com/jonatasoli - login: Kludex - count: 4 + count: 7 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex -- login: yinziyan1206 +- login: abhint + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=5b9f9f6192c83ca86a411eafd4be46d9e5828585&v=4 + url: https://github.com/abhint +- login: chrisK824 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 + url: https://github.com/chrisK824 +- login: djimontyp + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/53098395?u=583bade70950b277c322d35f1be2b75c7b0f189c&v=4 + url: https://github.com/djimontyp +- login: JavierSanchezCastro count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/37829370?v=4 - url: https://github.com/yinziyan1206 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro top_contributors: - login: waynerv count: 25 @@ -227,14 +231,18 @@ top_contributors: count: 22 avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 url: https://github.com/tokusumi +- login: Kludex + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 + url: https://github.com/Kludex +- login: jaystone776 + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 + url: https://github.com/jaystone776 - login: dmontagu count: 16 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 url: https://github.com/dmontagu -- login: jaystone776 - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 - url: https://github.com/jaystone776 - login: euri10 count: 13 avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 @@ -243,30 +251,42 @@ top_contributors: count: 12 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 url: https://github.com/mariacamilagl +- login: Xewus + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 + url: https://github.com/Xewus - login: Smlep - count: 9 + count: 10 avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 url: https://github.com/Smlep - login: Serrones count: 8 avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 url: https://github.com/Serrones -- login: Kludex +- login: rjNemo count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex + avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 + url: https://github.com/rjNemo - login: RunningIkkyu count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 url: https://github.com/RunningIkkyu - login: hard-coders count: 7 avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 url: https://github.com/hard-coders +- login: batlopes + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/33462923?u=0fb3d7acb316764616f11e4947faf080e49ad8d9&v=4 + url: https://github.com/batlopes - login: wshayes count: 5 avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 url: https://github.com/wshayes +- login: samuelcolvin + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4 + url: https://github.com/samuelcolvin - login: SwftAlpc count: 5 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 @@ -275,6 +295,14 @@ top_contributors: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 url: https://github.com/Attsun1031 +- login: ComicShrimp + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=f440bc9062afb3c43b9b9c6cdfdcfe31d58699ef&v=4 + url: https://github.com/ComicShrimp +- login: NinaHwang + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=eee6bfe9224c71193025ab7477f4f96ceaa05c62&v=4 + url: https://github.com/NinaHwang - login: jekirl count: 4 avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4 @@ -285,21 +313,41 @@ top_contributors: url: https://github.com/jfunez - login: ycd count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4 url: https://github.com/ycd - login: komtaki count: 4 avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 url: https://github.com/komtaki -- login: NinaHwang +- login: hitrust count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4 - url: https://github.com/NinaHwang + avatarUrl: https://avatars.githubusercontent.com/u/3360631?u=5fa1f475ad784d64eb9666bdd43cc4d285dcc773&v=4 + url: https://github.com/hitrust +- login: lsglucas + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 + url: https://github.com/lsglucas +- login: axel584 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 + url: https://github.com/axel584 top_reviewers: - login: Kludex - count: 93 + count: 117 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 url: https://github.com/Kludex +- login: BilalAlpaslan + count: 75 + avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 + url: https://github.com/BilalAlpaslan +- login: yezz123 + count: 74 + avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=d7062cbc6eb7671d5dc9cc0e32a24ae335e0f225&v=4 + url: https://github.com/yezz123 +- login: tokusumi + count: 51 + avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 + url: https://github.com/tokusumi - login: waynerv count: 47 avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 @@ -308,54 +356,78 @@ top_reviewers: count: 47 avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4 url: https://github.com/Laineyzhang55 -- login: tokusumi - count: 46 - avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 - url: https://github.com/tokusumi - login: ycd count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4 url: https://github.com/ycd +- login: iudeen + count: 44 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 + url: https://github.com/iudeen - login: cikay count: 41 avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4 url: https://github.com/cikay -- login: BilalAlpaslan - count: 40 - avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 - url: https://github.com/BilalAlpaslan +- login: JarroVGIT + count: 34 + avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 + url: https://github.com/JarroVGIT - login: AdrianDeAnda count: 33 - avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=bb7f8a0d6c9de4e9d0320a9f271210206e202250&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=b2ea249c6b41ddf98679c8d110d0f67d4a3ebf93&v=4 url: https://github.com/AdrianDeAnda +- login: Xewus + count: 32 + avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 + url: https://github.com/Xewus - login: ArcLightSlavik count: 31 avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 url: https://github.com/ArcLightSlavik - login: cassiobotaro - count: 25 + count: 28 avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4 url: https://github.com/cassiobotaro +- login: komtaki + count: 27 + avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 + url: https://github.com/komtaki +- login: lsglucas + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 + url: https://github.com/lsglucas +- login: Ryandaydev + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=809f3d1074d04bbc28012a7f17f06ea56f5bd71a&v=4 + url: https://github.com/Ryandaydev - login: dmontagu count: 23 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 url: https://github.com/dmontagu -- login: komtaki +- login: LorhanSohaky + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 + url: https://github.com/LorhanSohaky +- login: rjNemo count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 - url: https://github.com/komtaki -- login: yezz123 - count: 19 - avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4 - url: https://github.com/yezz123 + avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 + url: https://github.com/rjNemo - login: hard-coders - count: 19 + count: 21 avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 url: https://github.com/hard-coders +- login: odiseo0 + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=2da05dab6cc8e1ade557801634760a56e4101796&v=4 + url: https://github.com/odiseo0 - login: 0417taehyun count: 19 avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4 url: https://github.com/0417taehyun +- login: Smlep + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 + url: https://github.com/Smlep - login: zy7y count: 17 avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4 @@ -364,18 +436,10 @@ top_reviewers: count: 16 avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4 url: https://github.com/yanever -- login: lsglucas - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 - url: https://github.com/lsglucas - login: SwftAlpc count: 16 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 url: https://github.com/SwftAlpc -- login: Smlep - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 - url: https://github.com/Smlep - login: DevDae count: 16 avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4 @@ -388,22 +452,38 @@ top_reviewers: count: 15 avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 url: https://github.com/delhi09 -- login: rjNemo - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 - url: https://github.com/rjNemo -- login: RunningIkkyu - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4 - url: https://github.com/RunningIkkyu - login: sh0nk - count: 12 + count: 13 avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 url: https://github.com/sh0nk +- login: peidrao + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=5b94b548ef0002ef3219d7c07ac0fac17c6201a2&v=4 + url: https://github.com/peidrao +- login: r0b2g1t + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/5357541?u=6428442d875d5d71aaa1bb38bb11c4be1a526bc2&v=4 + url: https://github.com/r0b2g1t +- login: RunningIkkyu + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 + url: https://github.com/RunningIkkyu +- login: axel584 + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 + url: https://github.com/axel584 +- login: solomein-sv + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4 + url: https://github.com/solomein-sv - login: mariacamilagl count: 10 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 url: https://github.com/mariacamilagl +- login: raphaelauv + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 + url: https://github.com/raphaelauv - login: Attsun1031 count: 10 avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 @@ -412,13 +492,21 @@ top_reviewers: count: 10 avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 url: https://github.com/maoyibo -- login: solomein-sv +- login: ComicShrimp count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4 - url: https://github.com/solomein-sv + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=f440bc9062afb3c43b9b9c6cdfdcfe31d58699ef&v=4 + url: https://github.com/ComicShrimp +- login: Alexandrhub + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 + url: https://github.com/Alexandrhub +- login: izaguerreiro + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4 + url: https://github.com/izaguerreiro - login: graingert count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4 + avatarUrl: https://avatars.githubusercontent.com/u/413772?u=64b77b6aa405c68a9c6bcf45f84257c66eea5f32&v=4 url: https://github.com/graingert - login: PandaHun count: 9 @@ -432,63 +520,11 @@ top_reviewers: count: 9 avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4 url: https://github.com/bezaca -- login: blt232018 - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4 - url: https://github.com/blt232018 -- login: rogerbrinkmann - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/5690226?v=4 - url: https://github.com/rogerbrinkmann -- login: ComicShrimp - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4 - url: https://github.com/ComicShrimp +- login: oandersonmagalhaes + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4 + url: https://github.com/oandersonmagalhaes - login: NinaHwang - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=eee6bfe9224c71193025ab7477f4f96ceaa05c62&v=4 url: https://github.com/NinaHwang -- login: dimaqq - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4 - url: https://github.com/dimaqq -- login: Serrones - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 - url: https://github.com/Serrones -- login: ryuckel - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/36391432?u=094eec0cfddd5013f76f31e55e56147d78b19553&v=4 - url: https://github.com/ryuckel -- login: raphaelauv - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 - url: https://github.com/raphaelauv -- login: NastasiaSaby - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/8245071?u=b3afd005f9e4bf080c219ef61a592b3a8004b764&v=4 - url: https://github.com/NastasiaSaby -- login: Mause - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 - url: https://github.com/Mause -- login: AlexandreBiguet - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/1483079?u=ff926455cd4cab03c6c49441aa5dc2b21df3e266&v=4 - url: https://github.com/AlexandreBiguet -- login: krocdort - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4 - url: https://github.com/krocdort -- login: jovicon - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4 - url: https://github.com/jovicon -- login: LorhanSohaky - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 - url: https://github.com/LorhanSohaky -- login: peidrao - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=88c2cb42a99e0f50cdeae3606992568184783ee5&v=4 - url: https://github.com/peidrao diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index ac825193b7..1b5240b5e3 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -1,23 +1,14 @@ gold: - - url: https://bit.ly/2QSouzH - title: "Jina: build neural search-as-a-service for any kind of data in just minutes." - img: https://fastapi.tiangolo.com/img/sponsors/jina.svg - url: https://cryptapi.io/ title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway." img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg - - url: https://classiq.link/n4s - title: Join the team building a new SaaS platform that will change the computing world - img: https://fastapi.tiangolo.com/img/sponsors/classiq.png - - url: https://www.dropbase.io/careers - title: Dropbase - seamlessly collect, clean, and centralize data. - img: https://fastapi.tiangolo.com/img/sponsors/dropbase.svg + - url: https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023 + title: "Build, run and scale your apps on a modern, reliable, and secure PaaS." + img: https://fastapi.tiangolo.com/img/sponsors/platform-sh.png silver: - url: https://www.deta.sh/?ref=fastapi title: The launchpad for all your (team's) ideas img: https://fastapi.tiangolo.com/img/sponsors/deta.svg - - url: https://www.investsuite.com/jobs - title: Wealthtech jobs with FastAPI - img: https://fastapi.tiangolo.com/img/sponsors/investsuite.svg - url: https://training.talkpython.fm/fastapi-courses title: FastAPI video courses on demand from people you trust img: https://fastapi.tiangolo.com/img/sponsors/talkpython.png @@ -27,16 +18,19 @@ silver: - url: https://github.com/deepset-ai/haystack/ title: Build powerful search from composable, open source building blocks img: https://fastapi.tiangolo.com/img/sponsors/haystack-fastapi.svg - - url: https://www.udemy.com/course/fastapi-rest/ - title: Learn FastAPI by building a complete project. Extend your knowledge on advanced web development-AWS, Payments, Emails. - img: https://fastapi.tiangolo.com/img/sponsors/ines-course.jpg - - url: https://careers.budget-insight.com/ - title: Budget Insight is hiring! - img: https://fastapi.tiangolo.com/img/sponsors/budget-insight.svg + - url: https://careers.powens.com/ + title: Powens is hiring! + img: https://fastapi.tiangolo.com/img/sponsors/powens.png + - url: https://www.svix.com/ + title: Svix - Webhooks as a service + img: https://fastapi.tiangolo.com/img/sponsors/svix.svg + - url: https://databento.com/ + title: Pay as you go for market data + img: https://fastapi.tiangolo.com/img/sponsors/databento.svg bronze: - url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source title: Biosecurity risk assessments made easy. img: https://fastapi.tiangolo.com/img/sponsors/exoflare.png - - url: https://striveworks.us/careers?utm_source=fastapi&utm_medium=sponsor_banner&utm_campaign=feb_march#openings - title: https://striveworks.us/careers - img: https://fastapi.tiangolo.com/img/sponsors/striveworks.png + - url: https://www.flint.sh + title: IT expertise, consulting and development by passionate people + img: https://fastapi.tiangolo.com/img/sponsors/flint.png diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 1c8b0cde71..b3cb063270 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -5,8 +5,15 @@ logins: - mikeckennedy - deepset-ai - cryptapi - - Striveworks - xoflare - - InesIvanova - DropbaseHQ - VincentParedes + - BLUE-DEVIL1134 + - ObliviousAI + - Doist + - nihpo + - svix + - armand-sauzay + - databento-bot + - nanram22 + - Flint-company diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md index 5d136da41b..dca5f6a985 100644 --- a/docs/en/docs/advanced/additional-responses.md +++ b/docs/en/docs/advanced/additional-responses.md @@ -23,7 +23,7 @@ Each of those response `dict`s can have a key `model`, containing a Pydantic mod For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write: -```Python hl_lines="18 23" +```Python hl_lines="18 22" {!../../../docs_src/additional_responses/tutorial001.py!} ``` diff --git a/docs/en/docs/advanced/additional-status-codes.md b/docs/en/docs/advanced/additional-status-codes.md index 37ec283ffe..416444d3bd 100644 --- a/docs/en/docs/advanced/additional-status-codes.md +++ b/docs/en/docs/advanced/additional-status-codes.md @@ -14,9 +14,41 @@ But you also want it to accept new items. And when the items didn't exist before To achieve that, import `JSONResponse`, and return your content there directly, setting the `status_code` that you want: -```Python hl_lines="4 23" -{!../../../docs_src/additional_status_codes/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="4 25" + {!> ../../../docs_src/additional_status_codes/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 25" + {!> ../../../docs_src/additional_status_codes/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 26" + {!> ../../../docs_src/additional_status_codes/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 23" + {!> ../../../docs_src/additional_status_codes/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 25" + {!> ../../../docs_src/additional_status_codes/tutorial001.py!} + ``` !!! warning When you return a `Response` directly, like in the example above, it will be returned directly. diff --git a/docs/en/docs/advanced/advanced-dependencies.md b/docs/en/docs/advanced/advanced-dependencies.md index 5e79776cac..402c5d7553 100644 --- a/docs/en/docs/advanced/advanced-dependencies.md +++ b/docs/en/docs/advanced/advanced-dependencies.md @@ -18,9 +18,26 @@ Not the class itself (which is already a callable), but an instance of that clas To do that, we declare a method `__call__`: -```Python hl_lines="10" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="12" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` In this case, this `__call__` is what **FastAPI** will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your *path operation function* later. @@ -28,9 +45,26 @@ In this case, this `__call__` is what **FastAPI** will use to check for addition And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency: -```Python hl_lines="7" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code. @@ -38,9 +72,26 @@ In this case, **FastAPI** won't ever touch or care about `__init__`, we will use We could create an instance of this class with: -```Python hl_lines="16" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="18" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="16" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`. @@ -56,9 +107,26 @@ checker(q="somequery") ...and pass whatever that returns as the value of the dependency in our *path operation function* as the parameter `fixed_content_included`: -```Python hl_lines="20" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="22" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="21" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="20" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` !!! tip All this might seem contrived. And it might not be very clear how is it useful yet. diff --git a/docs/en/docs/advanced/async-tests.md b/docs/en/docs/advanced/async-tests.md index d5116233f8..9b39d70fca 100644 --- a/docs/en/docs/advanced/async-tests.md +++ b/docs/en/docs/advanced/async-tests.md @@ -1,6 +1,6 @@ # Async Tests -You have already seen how to test your **FastAPI** applications using the provided `TestClient`, but with it, you can't test or run any other `async` function in your (synchronous) pytest functions. +You have already seen how to test your **FastAPI** applications using the provided `TestClient`. Up to now, you have only seen how to write synchronous tests, without using `async` functions. Being able to use asynchronous functions in your tests could be useful, for example, when you're querying your database asynchronously. Imagine you want to test sending requests to your FastAPI application and then verify that your backend successfully wrote the correct data in the database, while using an async database library. @@ -8,7 +8,7 @@ Let's look at how we can make that work. ## pytest.mark.anyio -If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. Anyio provides a neat plugin for this, that allows us to specify that some test functions are to be called asynchronously. +If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. AnyIO provides a neat plugin for this, that allows us to specify that some test functions are to be called asynchronously. ## HTTPX @@ -16,23 +16,27 @@ Even if your **FastAPI** application uses normal `def` functions instead of `asy The `TestClient` does some magic inside to call the asynchronous FastAPI application in your normal `def` test functions, using standard pytest. But that magic doesn't work anymore when we're using it inside asynchronous functions. By running our tests asynchronously, we can no longer use the `TestClient` inside our test functions. -Luckily there's a nice alternative, called HTTPX. - -HTTPX is an HTTP client for Python 3 that allows us to query our FastAPI application similarly to how we did it with the `TestClient`. - -If you're familiar with the Requests library, you'll find that the API of HTTPX is almost identical. - -The important difference for us is that with HTTPX we are not limited to synchronous, but can also make asynchronous requests. +The `TestClient` is based on HTTPX, and luckily, we can use it directly to test the API. ## Example -For a simple example, let's consider the following `main.py` module: +For a simple example, let's consider a file structure similar to the one described in [Bigger Applications](../tutorial/bigger-applications.md){.internal-link target=_blank} and [Testing](../tutorial/testing.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +The file `main.py` would have: ```Python {!../../../docs_src/async_tests/main.py!} ``` -The `test_main.py` module that contains the tests for `main.py` could look like this now: +The file `test_main.py` would have the tests for `main.py`, it could look like this now: ```Python {!../../../docs_src/async_tests/test_main.py!} @@ -75,7 +79,7 @@ This is the equivalent to: response = client.get('/') ``` -that we used to make our requests with the `TestClient`. +...that we used to make our requests with the `TestClient`. !!! tip Note that we're using async/await with the new `AsyncClient` - the request is asynchronous. diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index 766a218aa3..03198851a3 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -251,7 +251,7 @@ We get the same response: but this time at the URL with the prefix path provided by the proxy: `/api/v1`. -Of course, the idea here is that everyone would access the app through the proxy, so the version with the path prefix `/app/v1` is the "correct" one. +Of course, the idea here is that everyone would access the app through the proxy, so the version with the path prefix `/api/v1` is the "correct" one. And the version without the path prefix (`http://127.0.0.1:8000/app`), provided by Uvicorn directly, would be exclusively for the _proxy_ (Traefik) to access it. diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index 546adad2a1..ce2619e8de 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -21,6 +21,12 @@ For example, if you are squeezing performance, you can install and use `orjson`, but with some custom settings not used in the included `ORJSONResponse` class. + +Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`. + +You could create a `CustomORJSONResponse`. The main thing you have to do is create a `Response.render(content)` method that returns the content as `bytes`: + +```Python hl_lines="9-14 17" +{!../../../docs_src/custom_response/tutorial009c.py!} +``` + +Now instead of returning: + +```json +{"message": "Hello World"} +``` + +...this response will return: + +```json +{ + "message": "Hello World" +} +``` + +Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉 + ## Default response class When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default. diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md index 80a063bb87..72daca06ad 100644 --- a/docs/en/docs/advanced/dataclasses.md +++ b/docs/en/docs/advanced/dataclasses.md @@ -8,7 +8,7 @@ But FastAPI also supports using internal support for `dataclasses`. +This is still supported thanks to **Pydantic**, as it has internal support for `dataclasses`. So, even with the code above that doesn't use Pydantic explicitly, FastAPI is using Pydantic to convert those standard dataclasses to Pydantic's own flavor of dataclasses. diff --git a/docs/en/docs/advanced/events.md b/docs/en/docs/advanced/events.md index 7cd2998f05..6b7de41309 100644 --- a/docs/en/docs/advanced/events.md +++ b/docs/en/docs/advanced/events.md @@ -1,13 +1,108 @@ -# Events: startup - shutdown +# Lifespan Events + +You can define logic (code) that should be executed before the application **starts up**. This means that this code will be executed **once**, **before** the application **starts receiving requests**. + +The same way, you can define logic (code) that should be executed when the application is **shutting down**. In this case, this code will be executed **once**, **after** having handled possibly **many requests**. + +Because this code is executed before the application **starts** taking requests, and right after it **finishes** handling requests, it covers the whole application **lifespan** (the word "lifespan" will be important in a second 😉). + +This can be very useful for setting up **resources** that you need to use for the whole app, and that are **shared** among requests, and/or that you need to **clean up** afterwards. For example, a database connection pool, or loading a shared machine learning model. + +## Use Case + +Let's start with an example **use case** and then see how to solve it with this. + +Let's imagine that you have some **machine learning models** that you want to use to handle requests. 🤖 + +The same models are shared among requests, so, it's not one model per request, or one per user or something similar. + +Let's imagine that loading the model can **take quite some time**, because it has to read a lot of **data from disk**. So you don't want to do it for every request. + +You could load it at the top level of the module/file, but that would also mean that it would **load the model** even if you are just running a simple automated test, then that test would be **slow** because it would have to wait for the model to load before being able to run an independent part of the code. + +That's what we'll solve, let's load the model before the requests are handled, but only right before the application starts receiving requests, not while the code is being loaded. + +## Lifespan + +You can define this *startup* and *shutdown* logic using the `lifespan` parameter of the `FastAPI` app, and a "context manager" (I'll show you what that is in a second). + +Let's start with an example and then see it in detail. + +We create an async function `lifespan()` with `yield` like this: + +```Python hl_lines="16 19" +{!../../../docs_src/events/tutorial003.py!} +``` + +Here we are simulating the expensive *startup* operation of loading the model by putting the (fake) model function in the dictionary with machine learning models before the `yield`. This code will be executed **before** the application **starts taking requests**, during the *startup*. + +And then, right after the `yield`, we unload the model. This code will be executed **after** the application **finishes handling requests**, right before the *shutdown*. This could, for example, release resources like memory or a GPU. + +!!! tip + The `shutdown` would happen when you are **stopping** the application. + + Maybe you need to start a new version, or you just got tired of running it. 🤷 + +### Lifespan function + +The first thing to notice, is that we are defining an async function with `yield`. This is very similar to Dependencies with `yield`. + +```Python hl_lines="14-19" +{!../../../docs_src/events/tutorial003.py!} +``` + +The first part of the function, before the `yield`, will be executed **before** the application starts. + +And the part after the `yield` will be executed **after** the application has finished. + +### Async Context Manager + +If you check, the function is decorated with an `@asynccontextmanager`. + +That converts the function into something called an "**async context manager**". + +```Python hl_lines="1 13" +{!../../../docs_src/events/tutorial003.py!} +``` + +A **context manager** in Python is something that you can use in a `with` statement, for example, `open()` can be used as a context manager: + +```Python +with open("file.txt") as file: + file.read() +``` + +In recent versions of Python, there's also an **async context manager**. You would use it with `async with`: + +```Python +async with lifespan(app): + await do_stuff() +``` + +When you create a context manager or an async context manager like above, what it does is that, before entering the `with` block, it will execute the code before the `yield`, and after exiting the `with` block, it will execute the code after the `yield`. + +In our code example above, we don't use it directly, but we pass it to FastAPI for it to use it. + +The `lifespan` parameter of the `FastAPI` app takes an **async context manager**, so we can pass our new `lifespan` async context manager to it. + +```Python hl_lines="22" +{!../../../docs_src/events/tutorial003.py!} +``` + +## Alternative Events (deprecated) + +!!! warning + The recommended way to handle the *startup* and *shutdown* is using the `lifespan` parameter of the `FastAPI` app as described above. + + You can probably skip this part. + +There's an alternative way to define this logic to be executed during *startup* and during *shutdown*. You can define event handlers (functions) that need to be executed before the application starts up, or when the application is shutting down. These functions can be declared with `async def` or normal `def`. -!!! warning - Only event handlers for the main application will be executed, not for [Sub Applications - Mounts](./sub-applications.md){.internal-link target=_blank}. - -## `startup` event +### `startup` event To add a function that should be run before the application starts, declare it with the event `"startup"`: @@ -21,7 +116,7 @@ You can add more than one event handler function. And your application won't start receiving requests until all the `startup` event handlers have completed. -## `shutdown` event +### `shutdown` event To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`: @@ -43,5 +138,25 @@ Here, the `shutdown` event handler function will write a text line `"Application So, we declare the event handler function with standard `def` instead of `async def`. +### `startup` and `shutdown` together + +There's a high chance that the logic for your *startup* and *shutdown* is connected, you might want to start something and then finish it, acquire a resource and then release it, etc. + +Doing that in separated functions that don't share logic or variables together is more difficult as you would need to store values in global variables or similar tricks. + +Because of that, it's now recommended to instead use the `lifespan` as explained above. + +## Technical Details + +Just a technical detail for the curious nerds. 🤓 + +Underneath, in the ASGI technical specification, this is part of the Lifespan Protocol, and it defines events called `startup` and `shutdown`. + !!! info - You can read more about these event handlers in Starlette's Events' docs. + You can read more about the Starlette `lifespan` handlers in Starlette's Lifespan' docs. + + Including how to handle lifespan state that can be used in other areas of your code. + +## Sub Applications + +🚨 Have in mind that these lifespan events (startup and shutdown) will only be executed for the main application, not for [Sub Applications - Mounts](./sub-applications.md){.internal-link target=_blank}. diff --git a/docs/en/docs/advanced/generate-clients.md b/docs/en/docs/advanced/generate-clients.md index f31a248d0b..f62c0b57c1 100644 --- a/docs/en/docs/advanced/generate-clients.md +++ b/docs/en/docs/advanced/generate-clients.md @@ -16,18 +16,18 @@ If you are building a **frontend**, a very interesting alternative is ../../../docs_src/generate_clients/tutorial001.py!} - ``` - -=== "Python 3.9 and above" +=== "Python 3.9+" ```Python hl_lines="7-9 12-13 16-17 21" {!> ../../../docs_src/generate_clients/tutorial001_py39.py!} ``` +=== "Python 3.6+" + + ```Python hl_lines="9-11 14-15 18 19 23" + {!> ../../../docs_src/generate_clients/tutorial001.py!} + ``` + Notice that the *path operations* define the models they use for request payload and response payload, using the models `Item` and `ResponseMessage`. ### API Docs @@ -128,19 +128,18 @@ In many cases your FastAPI app will be bigger, and you will probably use tags to For example, you could have a section for **items** and another section for **users**, and they could be separated by tags: - -=== "Python 3.6 and above" - - ```Python hl_lines="23 28 36" - {!> ../../../docs_src/generate_clients/tutorial002.py!} - ``` - -=== "Python 3.9 and above" +=== "Python 3.9+" ```Python hl_lines="21 26 34" {!> ../../../docs_src/generate_clients/tutorial002_py39.py!} ``` +=== "Python 3.6+" + + ```Python hl_lines="23 28 36" + {!> ../../../docs_src/generate_clients/tutorial002.py!} + ``` + ### Generate a TypeScript Client with Tags If you generate a client for a FastAPI app using tags, it will normally also separate the client code based on the tags. @@ -186,18 +185,18 @@ For example, here it is using the first tag (you will probably have only one tag You can then pass that custom function to **FastAPI** as the `generate_unique_id_function` parameter: -=== "Python 3.6 and above" - - ```Python hl_lines="8-9 12" - {!> ../../../docs_src/generate_clients/tutorial003.py!} - ``` - -=== "Python 3.9 and above" +=== "Python 3.9+" ```Python hl_lines="6-7 10" {!> ../../../docs_src/generate_clients/tutorial003_py39.py!} ``` +=== "Python 3.6+" + + ```Python hl_lines="8-9 12" + {!> ../../../docs_src/generate_clients/tutorial003.py!} + ``` + ### Generate a TypeScript Client with Custom Operation IDs Now if you generate the client again, you will see that it has the improved method names: diff --git a/docs/en/docs/advanced/index.md b/docs/en/docs/advanced/index.md index 917f4a62eb..467f0833e6 100644 --- a/docs/en/docs/advanced/index.md +++ b/docs/en/docs/advanced/index.md @@ -1,4 +1,4 @@ -# Advanced User Guide - Intro +# Advanced User Guide ## Additional Features diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md index ed90f29be7..9219f1d2cb 100644 --- a/docs/en/docs/advanced/middleware.md +++ b/docs/en/docs/advanced/middleware.md @@ -68,7 +68,7 @@ Enforces that all incoming requests have a correctly set `Host` header, in order The following arguments are supported: -* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard domains such as `*.example.com` are supported for matching subdomains to allow any hostname either use `allowed_hosts=["*"]` or omit the middleware. +* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard domains such as `*.example.com` are supported for matching subdomains. To allow any hostname either use `allowed_hosts=["*"]` or omit the middleware. If an incoming request does not validate correctly then a `400` response will be sent. @@ -92,7 +92,7 @@ There are many other ASGI middlewares. For example: -* Sentry +* Sentry * Uvicorn's `ProxyHeadersMiddleware` * MessagePack diff --git a/docs/en/docs/advanced/openapi-callbacks.md b/docs/en/docs/advanced/openapi-callbacks.md index 138c90dd7c..71924ce8b2 100644 --- a/docs/en/docs/advanced/openapi-callbacks.md +++ b/docs/en/docs/advanced/openapi-callbacks.md @@ -31,7 +31,7 @@ It will have a *path operation* that will receive an `Invoice` body, and a query This part is pretty normal, most of the code is probably already familiar to you: -```Python hl_lines="10-14 37-54" +```Python hl_lines="9-13 36-53" {!../../../docs_src/openapi_callbacks/tutorial001.py!} ``` @@ -50,7 +50,7 @@ It could be just one or two lines of code, like: ```Python callback_url = "https://example.com/api/v1/invoices/events/" -requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) ``` But possibly the most important part of the callback is making sure that your API user (the external developer) implements the *external API* correctly, according to the data that *your API* is going to send in the request body of the callback, etc. @@ -64,7 +64,7 @@ This example doesn't implement the callback itself (that could be just a line of !!! tip The actual callback is just an HTTP request. - When implementing the callback yourself, you could use something like HTTPX or Requests. + When implementing the callback yourself, you could use something like HTTPX or Requests. ## Write the callback documentation code @@ -83,7 +83,7 @@ So we are going to use that same knowledge to document how the *external API* sh First create a new `APIRouter` that will contain one or more callbacks. -```Python hl_lines="5 26" +```Python hl_lines="3 25" {!../../../docs_src/openapi_callbacks/tutorial001.py!} ``` @@ -96,7 +96,7 @@ It should look just like a normal FastAPI *path operation*: * It should probably have a declaration of the body it should receive, e.g. `body: InvoiceEvent`. * And it could also have a declaration of the response it should return, e.g. `response_model=InvoiceEventReceived`. -```Python hl_lines="17-19 22-23 29-33" +```Python hl_lines="16-18 21-22 28-32" {!../../../docs_src/openapi_callbacks/tutorial001.py!} ``` @@ -163,7 +163,7 @@ At this point you have the *callback path operation(s)* needed (the one(s) that Now use the parameter `callbacks` in *your API's path operation decorator* to pass the attribute `.routes` (that's actually just a `list` of routes/*path operations*) from that callback router: -```Python hl_lines="36" +```Python hl_lines="35" {!../../../docs_src/openapi_callbacks/tutorial001.py!} ``` diff --git a/docs/en/docs/advanced/security/http-basic-auth.md b/docs/en/docs/advanced/security/http-basic-auth.md index 6c589cd9af..8177a4b289 100644 --- a/docs/en/docs/advanced/security/http-basic-auth.md +++ b/docs/en/docs/advanced/security/http-basic-auth.md @@ -20,9 +20,26 @@ Then, when you type that username and password, the browser sends them in the he * It returns an object of type `HTTPBasicCredentials`: * It contains the `username` and `password` sent. -```Python hl_lines="2 6 10" -{!../../../docs_src/security/tutorial006.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="4 8 12" + {!> ../../../docs_src/security/tutorial006_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="2 7 11" + {!> ../../../docs_src/security/tutorial006_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 6 10" + {!> ../../../docs_src/security/tutorial006.py!} + ``` When you try to open the URL for the first time (or click the "Execute" button in the docs) the browser will ask you for your username and password: @@ -34,13 +51,36 @@ Here's a more complete example. Use a dependency to check if the username and password are correct. -For this, use the Python standard module `secrets` to check the username and password: +For this, use the Python standard module `secrets` to check the username and password. -```Python hl_lines="1 11-13" -{!../../../docs_src/security/tutorial007.py!} -``` +`secrets.compare_digest()` needs to take `bytes` or a `str` that only contains ASCII characters (the ones in English), this means it wouldn't work with characters like `á`, as in `Sebastián`. -This will ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. This would be similar to: +To handle that, we first convert the `username` and `password` to `bytes` encoding them with UTF-8. + +Then we can use `secrets.compare_digest()` to ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. + +=== "Python 3.9+" + + ```Python hl_lines="1 12-24" + {!> ../../../docs_src/security/tutorial007_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 12-24" + {!> ../../../docs_src/security/tutorial007_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1 11-21" + {!> ../../../docs_src/security/tutorial007.py!} + ``` + +This would be similar to: ```Python if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): @@ -102,6 +142,23 @@ That way, using `secrets.compare_digest()` in your application code, it will be After detecting that the credentials are incorrect, return an `HTTPException` with a status code 401 (the same returned when no credentials are provided) and add the header `WWW-Authenticate` to make the browser show the login prompt again: -```Python hl_lines="15-19" -{!../../../docs_src/security/tutorial007.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="26-30" + {!> ../../../docs_src/security/tutorial007_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="26-30" + {!> ../../../docs_src/security/tutorial007_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="23-27" + {!> ../../../docs_src/security/tutorial007.py!} + ``` diff --git a/docs/en/docs/advanced/security/index.md b/docs/en/docs/advanced/security/index.md index 0c94986b57..c18baf64b0 100644 --- a/docs/en/docs/advanced/security/index.md +++ b/docs/en/docs/advanced/security/index.md @@ -1,4 +1,4 @@ -# Advanced Security - Intro +# Advanced Security ## Additional Features diff --git a/docs/en/docs/advanced/security/oauth2-scopes.md b/docs/en/docs/advanced/security/oauth2-scopes.md index be51325cdb..41cd61683d 100644 --- a/docs/en/docs/advanced/security/oauth2-scopes.md +++ b/docs/en/docs/advanced/security/oauth2-scopes.md @@ -56,9 +56,50 @@ They are normally used to declare specific security permissions, for example: First, let's quickly see the parts that change from the examples in the main **Tutorial - User Guide** for [OAuth2 with Password (and hashing), Bearer with JWT tokens](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Now using OAuth2 scopes: -```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 153" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="4 8 12 46 64 105 107-115 121-124 128-134 139 155" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 155" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="2 4 8 12 47 65 106 108-116 122-125 129-135 140 156" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3 7 11 45 63 104 106-114 120-123 127-133 138 152" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 153" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 153" + {!> ../../../docs_src/security/tutorial005.py!} + ``` Now let's review those changes step by step. @@ -68,9 +109,51 @@ The first change is that now we are declaring the OAuth2 security scheme with tw The `scopes` parameter receives a `dict` with each scope as a key and the description as the value: -```Python hl_lines="62-65" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="63-66" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="61-64" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005.py!} + ``` Because we are now declaring those scopes, they will show up in the API docs when you log-in/authorize. @@ -93,9 +176,50 @@ And we return the scopes as part of the JWT token. But in your application, for security, you should make sure you only add the scopes that the user is actually able to have, or the ones you have predefined. -```Python hl_lines="153" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="155" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="155" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="156" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="152" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="153" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="153" + {!> ../../../docs_src/security/tutorial005.py!} + ``` ## Declare scopes in *path operations* and dependencies @@ -118,9 +242,50 @@ In this case, it requires the scope `me` (it could require more than one scope). We are doing it here to demonstrate how **FastAPI** handles scopes declared at different levels. -```Python hl_lines="4 139 166" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="4 139 170" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 139 170" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 140 171" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3 138 165" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 139 166" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 139 166" + {!> ../../../docs_src/security/tutorial005.py!} + ``` !!! info "Technical Details" `Security` is actually a subclass of `Depends`, and it has just one extra parameter that we'll see later. @@ -143,9 +308,50 @@ We also declare a special parameter of type `SecurityScopes`, imported from `fas This `SecurityScopes` class is similar to `Request` (`Request` was used to get the request object directly). -```Python hl_lines="8 105" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8 106" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7 104" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005.py!} + ``` ## Use the `scopes` @@ -159,9 +365,50 @@ We create an `HTTPException` that we can re-use (`raise`) later at several point In this exception, we include the scopes required (if any) as a string separated by spaces (using `scope_str`). We put that string containing the scopes in the `WWW-Authenticate` header (this is part of the spec). -```Python hl_lines="105 107-115" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="106 108-116" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="104 106-114" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005.py!} + ``` ## Verify the `username` and data shape @@ -177,9 +424,50 @@ Instead of, for example, a `dict`, or something else, as it could break the appl We also verify that we have a user with that username, and if not, we raise that same exception we created before. -```Python hl_lines="46 116-127" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="47 117-128" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="45 115-126" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005.py!} + ``` ## Verify the `scopes` @@ -187,9 +475,50 @@ We now verify that all the scopes required, by this dependency and all the depen For this, we use `security_scopes.scopes`, that contains a `list` with all these scopes as `str`. -```Python hl_lines="128-134" -{!../../../docs_src/security/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="129-135" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="127-133" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005.py!} + ``` ## Dependency tree and scopes diff --git a/docs/en/docs/advanced/settings.md b/docs/en/docs/advanced/settings.md index d5151b585c..60ec9c92c6 100644 --- a/docs/en/docs/advanced/settings.md +++ b/docs/en/docs/advanced/settings.md @@ -216,9 +216,26 @@ Notice that now we don't create a default instance `settings = Settings()`. Now we create a dependency that returns a new `config.Settings()`. -```Python hl_lines="5 11-12" -{!../../../docs_src/settings/app02/main.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="6 12-13" + {!> ../../../docs_src/settings/app02_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="6 12-13" + {!> ../../../docs_src/settings/app02_an/main.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="5 11-12" + {!> ../../../docs_src/settings/app02/main.py!} + ``` !!! tip We'll discuss the `@lru_cache()` in a bit. @@ -227,9 +244,26 @@ Now we create a dependency that returns a new `config.Settings()`. And then we can require it from the *path operation function* as a dependency and use it anywhere we need it. -```Python hl_lines="16 18-20" -{!../../../docs_src/settings/app02/main.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="17 19-21" + {!> ../../../docs_src/settings/app02_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17 19-21" + {!> ../../../docs_src/settings/app02_an/main.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="16 18-20" + {!> ../../../docs_src/settings/app02/main.py!} + ``` ### Settings and testing @@ -304,9 +338,26 @@ we would create that object for each request, and we would be reading the `.env` But as we are using the `@lru_cache()` decorator on top, the `Settings` object will be created only once, the first time it's called. ✔️ -```Python hl_lines="1 10" -{!../../../docs_src/settings/app03/main.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="1 11" + {!> ../../../docs_src/settings/app03_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 11" + {!> ../../../docs_src/settings/app03_an/main.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1 10" + {!> ../../../docs_src/settings/app03/main.py!} + ``` Then for any subsequent calls of `get_settings()` in the dependencies for the next requests, instead of executing the internal code of `get_settings()` and creating a new `Settings` object, it will return the same object that was returned on the first call, again and again. diff --git a/docs/en/docs/advanced/testing-database.md b/docs/en/docs/advanced/testing-database.md index 16484b09af..13a6959b6f 100644 --- a/docs/en/docs/advanced/testing-database.md +++ b/docs/en/docs/advanced/testing-database.md @@ -44,7 +44,7 @@ So the new file structure looks like: First, we create a new database session with the new database. -For the tests we'll use a file `test.db` instead of `sql_app.db`. +We'll use an in-memory database that persists during the tests instead of the local file `sql_app.db`. But the rest of the session code is more or less the same, we just copy it. diff --git a/docs/en/docs/advanced/testing-dependencies.md b/docs/en/docs/advanced/testing-dependencies.md index 79208e8dc8..ee48a735d8 100644 --- a/docs/en/docs/advanced/testing-dependencies.md +++ b/docs/en/docs/advanced/testing-dependencies.md @@ -28,9 +28,41 @@ To override a dependency for testing, you put as a key the original dependency ( And then **FastAPI** will call that override instead of the original dependency. -```Python hl_lines="26-27 30" -{!../../../docs_src/dependency_testing/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="26-27 30" + {!> ../../../docs_src/dependency_testing/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="28-29 32" + {!> ../../../docs_src/dependency_testing/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="29-30 33" + {!> ../../../docs_src/dependency_testing/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="24-25 28" + {!> ../../../docs_src/dependency_testing/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="28-29 32" + {!> ../../../docs_src/dependency_testing/tutorial001.py!} + ``` !!! tip You can set a dependency override for a dependency used anywhere in your **FastAPI** application. diff --git a/docs/en/docs/advanced/websockets.md b/docs/en/docs/advanced/websockets.md index 878ad37ddc..94cf191d27 100644 --- a/docs/en/docs/advanced/websockets.md +++ b/docs/en/docs/advanced/websockets.md @@ -2,6 +2,20 @@ You can use WebSockets with **FastAPI**. +## Install `WebSockets` + +First you need to install `WebSockets`: + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ ## WebSockets client ### In production @@ -98,17 +112,47 @@ In WebSocket endpoints you can import from `fastapi` and use: They work the same way as for other FastAPI endpoints/*path operations*: -```Python hl_lines="58-65 68-83" -{!../../../docs_src/websockets/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="68-69 82" + {!> ../../../docs_src/websockets/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="68-69 82" + {!> ../../../docs_src/websockets/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="69-70 83" + {!> ../../../docs_src/websockets/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="66-67 79" + {!> ../../../docs_src/websockets/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="68-69 81" + {!> ../../../docs_src/websockets/tutorial002.py!} + ``` !!! info - In a WebSocket it doesn't really make sense to raise an `HTTPException`. So it's better to close the WebSocket connection directly. + As this is a WebSocket it doesn't really make sense to raise an `HTTPException`, instead we raise a `WebSocketException`. You can use a closing code from the valid codes defined in the specification. - In the future, there will be a `WebSocketException` that you will be able to `raise` from anywhere, and add exception handlers for it. It depends on the PR #527 in Starlette. - ### Try the WebSockets with dependencies If your file is named `main.py`, run your application with: @@ -141,9 +185,17 @@ With that you can connect the WebSocket and then send and receive messages: When a WebSocket connection is closed, the `await websocket.receive_text()` will raise a `WebSocketDisconnect` exception, which you can then catch and handle like in this example. -```Python hl_lines="81-83" -{!../../../docs_src/websockets/tutorial003.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="79-81" + {!> ../../../docs_src/websockets/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="81-83" + {!> ../../../docs_src/websockets/tutorial003.py!} + ``` To try it out: diff --git a/docs/en/docs/advanced/wsgi.md b/docs/en/docs/advanced/wsgi.md index df88659617..cfe3c78c11 100644 --- a/docs/en/docs/advanced/wsgi.md +++ b/docs/en/docs/advanced/wsgi.md @@ -12,7 +12,7 @@ Then wrap the WSGI (e.g. Flask) app with the middleware. And then mount that under a path. -```Python hl_lines="2-3 22" +```Python hl_lines="2-3 23" {!../../../docs_src/wsgi/tutorial001.py!} ``` diff --git a/docs/en/docs/alternatives.md b/docs/en/docs/alternatives.md index 68aa3150d7..0f074ccf32 100644 --- a/docs/en/docs/alternatives.md +++ b/docs/en/docs/alternatives.md @@ -123,7 +123,7 @@ That's why when talking about version 2.0 it's common to say "Swagger", and for There are several Flask REST frameworks, but after investing the time and work into investigating them, I found that many are discontinued or abandoned, with several standing issues that made them unfit. -### Marshmallow +### Marshmallow One of the main features needed by API systems is data "serialization" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc. @@ -367,7 +367,7 @@ It has: * WebSocket support. * In-process background tasks. * Startup and shutdown events. -* Test client built on requests. +* Test client built on HTTPX. * CORS, GZip, Static Files, Streaming responses. * Session and Cookie support. * 100% test coverage. diff --git a/docs/en/docs/async.md b/docs/en/docs/async.md index 71f2e75025..3d4b1956af 100644 --- a/docs/en/docs/async.md +++ b/docs/en/docs/async.md @@ -26,7 +26,7 @@ async def read_results(): --- -If you are using a third party library that communicates with something (a database, an API, the file system, etc) and doesn't have support for using `await`, (this is currently the case for most database libraries), then declare your *path operation functions* as normally, with just `def`, like: +If you are using a third party library that communicates with something (a database, an API, the file system, etc.) and doesn't have support for using `await`, (this is currently the case for most database libraries), then declare your *path operation functions* as normally, with just `def`, like: ```Python hl_lines="2" @app.get('/') @@ -45,7 +45,7 @@ If you just don't know, use normal `def`. --- -**Note**: you can mix `def` and `async def` in your *path operation functions* as much as you need and define each one using the best option for you. FastAPI will do the right thing with them. +**Note**: You can mix `def` and `async def` in your *path operation functions* as much as you need and define each one using the best option for you. FastAPI will do the right thing with them. Anyway, in any of the cases above, FastAPI will still work asynchronously and be extremely fast. @@ -102,87 +102,117 @@ To see the difference, imagine the following story about burgers: ### Concurrent Burgers - +You go with your crush to get fast food, you stand in line while the cashier takes the orders from the people in front of you. 😍 -You go with your crush 😍 to get fast food 🍔, you stand in line while the cashier 💁 takes the orders from the people in front of you. + -Then it's your turn, you place your order of 2 very fancy burgers 🍔 for your crush 😍 and you. +Then it's your turn, you place your order of 2 very fancy burgers for your crush and you. 🍔🍔 -You pay 💸. + -The cashier 💁 says something to the cook in the kitchen 👨‍🍳 so they know they have to prepare your burgers 🍔 (even though they are currently preparing the ones for the previous clients). +The cashier says something to the cook in the kitchen so they know they have to prepare your burgers (even though they are currently preparing the ones for the previous clients). -The cashier 💁 gives you the number of your turn. + -While you are waiting, you go with your crush 😍 and pick a table, you sit and talk with your crush 😍 for a long time (as your burgers are very fancy and take some time to prepare ✨🍔✨). +You pay. 💸 -As you are sitting at the table with your crush 😍, while you wait for the burgers 🍔, you can spend that time admiring how awesome, cute and smart your crush is ✨😍✨. +The cashier gives you the number of your turn. -While waiting and talking to your crush 😍, from time to time, you check the number displayed on the counter to see if it's your turn already. + -Then at some point, it finally is your turn. You go to the counter, get your burgers 🍔 and come back to the table. +While you are waiting, you go with your crush and pick a table, you sit and talk with your crush for a long time (as your burgers are very fancy and take some time to prepare). -You and your crush 😍 eat the burgers 🍔 and have a nice time ✨. +As you are sitting at the table with your crush, while you wait for the burgers, you can spend that time admiring how awesome, cute and smart your crush is ✨😍✨. + + + +While waiting and talking to your crush, from time to time, you check the number displayed on the counter to see if it's your turn already. + +Then at some point, it finally is your turn. You go to the counter, get your burgers and come back to the table. + + + +You and your crush eat the burgers and have a nice time. ✨ + + + +!!! info + Beautiful illustrations by Ketrina Thompson. 🎨 --- Imagine you are the computer / program 🤖 in that story. -While you are at the line, you are just idle 😴, waiting for your turn, not doing anything very "productive". But the line is fast because the cashier 💁 is only taking the orders (not preparing them), so that's fine. +While you are at the line, you are just idle 😴, waiting for your turn, not doing anything very "productive". But the line is fast because the cashier is only taking the orders (not preparing them), so that's fine. -Then, when it's your turn, you do actual "productive" work 🤓, you process the menu, decide what you want, get your crush's 😍 choice, pay 💸, check that you give the correct bill or card, check that you are charged correctly, check that the order has the correct items, etc. +Then, when it's your turn, you do actual "productive" work, you process the menu, decide what you want, get your crush's choice, pay, check that you give the correct bill or card, check that you are charged correctly, check that the order has the correct items, etc. -But then, even though you still don't have your burgers 🍔, your work with the cashier 💁 is "on pause" ⏸, because you have to wait 🕙 for your burgers to be ready. +But then, even though you still don't have your burgers, your work with the cashier is "on pause" ⏸, because you have to wait 🕙 for your burgers to be ready. -But as you go away from the counter and sit at the table with a number for your turn, you can switch 🔀 your attention to your crush 😍, and "work" ⏯ 🤓 on that. Then you are again doing something very "productive" 🤓, as is flirting with your crush 😍. +But as you go away from the counter and sit at the table with a number for your turn, you can switch 🔀 your attention to your crush, and "work" ⏯ 🤓 on that. Then you are again doing something very "productive" as is flirting with your crush 😍. -Then the cashier 💁 says "I'm finished with doing the burgers" 🍔 by putting your number on the counter's display, but you don't jump like crazy immediately when the displayed number changes to your turn number. You know no one will steal your burgers 🍔 because you have the number of your turn, and they have theirs. +Then the cashier 💁 says "I'm finished with doing the burgers" by putting your number on the counter's display, but you don't jump like crazy immediately when the displayed number changes to your turn number. You know no one will steal your burgers because you have the number of your turn, and they have theirs. -So you wait for your crush 😍 to finish the story (finish the current work ⏯ / task being processed 🤓), smile gently and say that you are going for the burgers ⏸. +So you wait for your crush to finish the story (finish the current work ⏯ / task being processed 🤓), smile gently and say that you are going for the burgers ⏸. -Then you go to the counter 🔀, to the initial task that is now finished ⏯, pick the burgers 🍔, say thanks and take them to the table. That finishes that step / task of interaction with the counter ⏹. That in turn, creates a new task, of "eating burgers" 🔀 ⏯, but the previous one of "getting burgers" is finished ⏹. +Then you go to the counter 🔀, to the initial task that is now finished ⏯, pick the burgers, say thanks and take them to the table. That finishes that step / task of interaction with the counter ⏹. That in turn, creates a new task, of "eating burgers" 🔀 ⏯, but the previous one of "getting burgers" is finished ⏹. ### Parallel Burgers Now let's imagine these aren't "Concurrent Burgers", but "Parallel Burgers". -You go with your crush 😍 to get parallel fast food 🍔. +You go with your crush to get parallel fast food. -You stand in line while several (let's say 8) cashiers that at the same time are cooks 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳 take the orders from the people in front of you. +You stand in line while several (let's say 8) cashiers that at the same time are cooks take the orders from the people in front of you. -Everyone before you is waiting 🕙 for their burgers 🍔 to be ready before leaving the counter because each of the 8 cashiers goes and prepares the burger right away before getting the next order. +Everyone before you is waiting for their burgers to be ready before leaving the counter because each of the 8 cashiers goes and prepares the burger right away before getting the next order. -Then it's finally your turn, you place your order of 2 very fancy burgers 🍔 for your crush 😍 and you. + + +Then it's finally your turn, you place your order of 2 very fancy burgers for your crush and you. You pay 💸. -The cashier goes to the kitchen 👨‍🍳. + -You wait, standing in front of the counter 🕙, so that no one else takes your burgers 🍔 before you do, as there are no numbers for turns. +The cashier goes to the kitchen. -As you and your crush 😍 are busy not letting anyone get in front of you and take your burgers whenever they arrive 🕙, you cannot pay attention to your crush 😞. +You wait, standing in front of the counter 🕙, so that no one else takes your burgers before you do, as there are no numbers for turns. -This is "synchronous" work, you are "synchronized" with the cashier/cook 👨‍🍳. You have to wait 🕙 and be there at the exact moment that the cashier/cook 👨‍🍳 finishes the burgers 🍔 and gives them to you, or otherwise, someone else might take them. + -Then your cashier/cook 👨‍🍳 finally comes back with your burgers 🍔, after a long time waiting 🕙 there in front of the counter. +As you and your crush are busy not letting anyone get in front of you and take your burgers whenever they arrive, you cannot pay attention to your crush. 😞 -You take your burgers 🍔 and go to the table with your crush 😍. +This is "synchronous" work, you are "synchronized" with the cashier/cook 👨‍🍳. You have to wait 🕙 and be there at the exact moment that the cashier/cook 👨‍🍳 finishes the burgers and gives them to you, or otherwise, someone else might take them. -You just eat them, and you are done 🍔 ⏹. + -There was not much talk or flirting as most of the time was spent waiting 🕙 in front of the counter 😞. +Then your cashier/cook 👨‍🍳 finally comes back with your burgers, after a long time waiting 🕙 there in front of the counter. + + + +You take your burgers and go to the table with your crush. + +You just eat them, and you are done. ⏹ + + + +There was not much talk or flirting as most of the time was spent waiting 🕙 in front of the counter. 😞 + +!!! info + Beautiful illustrations by Ketrina Thompson. 🎨 --- -In this scenario of the parallel burgers, you are a computer / program 🤖 with two processors (you and your crush 😍), both waiting 🕙 and dedicating their attention ⏯ to be "waiting on the counter" 🕙 for a long time. +In this scenario of the parallel burgers, you are a computer / program 🤖 with two processors (you and your crush), both waiting 🕙 and dedicating their attention ⏯ to be "waiting on the counter" 🕙 for a long time. -The fast food store has 8 processors (cashiers/cooks) 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳. While the concurrent burgers store might have had only 2 (one cashier and one cook) 💁 👨‍🍳. +The fast food store has 8 processors (cashiers/cooks). While the concurrent burgers store might have had only 2 (one cashier and one cook). -But still, the final experience is not the best 😞. +But still, the final experience is not the best. 😞 --- -This would be the parallel equivalent story for burgers 🍔. +This would be the parallel equivalent story for burgers. 🍔 For a more "real life" example of this, imagine a bank. @@ -208,11 +238,7 @@ This "waiting" 🕙 is measured in microseconds, but still, summing it all, it's That's why it makes a lot of sense to use asynchronous ⏸🔀⏯ code for web APIs. -Most of the existing popular Python frameworks (including Flask and Django) were created before the new asynchronous features in Python existed. So, the ways they can be deployed support parallel execution and an older form of asynchronous execution that is not as powerful as the new capabilities. - -Even though the main specification for asynchronous web Python (ASGI) was developed at Django, to add support for WebSockets. - -That kind of asynchronicity is what made NodeJS popular (even though NodeJS is not parallel) and that's the strength of Go as a programming language. +This kind of asynchronicity is what made NodeJS popular (even though NodeJS is not parallel) and that's the strength of Go as a programming language. And that's the same level of performance you get with **FastAPI**. @@ -238,7 +264,7 @@ You could have turns as in the burgers example, first the living room, then the It would take the same amount of time to finish with or without turns (concurrency) and you would have done the same amount of work. -But in this case, if you could bring the 8 ex-cashier/cooks/now-cleaners 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳, and each one of them (plus you) could take a zone of the house to clean it, you could do all the work in **parallel**, with the extra help, and finish much sooner. +But in this case, if you could bring the 8 ex-cashier/cooks/now-cleaners, and each one of them (plus you) could take a zone of the house to clean it, you could do all the work in **parallel**, with the extra help, and finish much sooner. In this scenario, each one of the cleaners (including you) would be a processor, doing their part of the job. @@ -257,7 +283,7 @@ For example: ### Concurrency + Parallelism: Web + Machine Learning -With **FastAPI** you can take the advantage of concurrency that is very common for web development (the same main attractive of NodeJS). +With **FastAPI** you can take the advantage of concurrency that is very common for web development (the same main attraction of NodeJS). But you can also exploit the benefits of parallelism and multiprocessing (having multiple processes running in parallel) for **CPU bound** workloads like those in Machine Learning systems. @@ -371,23 +397,23 @@ All that is what powers FastAPI (through Starlette) and what makes it have such These are very technical details of how **FastAPI** works underneath. - If you have quite some technical knowledge (co-routines, threads, blocking, etc) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead. + If you have quite some technical knowledge (co-routines, threads, blocking, etc.) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead. ### Path operation functions When you declare a *path operation function* with normal `def` instead of `async def`, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server). -If you are coming from another async framework that does not work in the way described above and you are used to define trivial compute-only *path operation functions* with plain `def` for a tiny performance gain (about 100 nanoseconds), please note that in **FastAPI** the effect would be quite opposite. In these cases, it's better to use `async def` unless your *path operation functions* use code that performs blocking I/O. +If you are coming from another async framework that does not work in the way described above and you are used to defining trivial compute-only *path operation functions* with plain `def` for a tiny performance gain (about 100 nanoseconds), please note that in **FastAPI** the effect would be quite opposite. In these cases, it's better to use `async def` unless your *path operation functions* use code that performs blocking I/O. Still, in both situations, chances are that **FastAPI** will [still be faster](/#performance){.internal-link target=_blank} than (or at least comparable to) your previous framework. ### Dependencies -The same applies for dependencies. If a dependency is a standard `def` function instead of `async def`, it is run in the external threadpool. +The same applies for [dependencies](/tutorial/dependencies/index.md){.internal-link target=_blank}. If a dependency is a standard `def` function instead of `async def`, it is run in the external threadpool. ### Sub-dependencies -You can have multiple dependencies and sub-dependencies requiring each other (as parameters of the function definitions), some of them might be created with `async def` and some with normal `def`. It would still work, and the ones created with normal `def` would be called on an external thread (from the threadpool) instead of being "awaited". +You can have multiple dependencies and [sub-dependencies](/tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} requiring each other (as parameters of the function definitions), some of them might be created with `async def` and some with normal `def`. It would still work, and the ones created with normal `def` would be called on an external thread (from the threadpool) instead of being "awaited". ### Other utility functions diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index 648c472fea..f968489aed 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -84,58 +84,36 @@ To check it worked, use: If it shows the `pip` binary at `env/bin/pip` then it worked. 🎉 - - -!!! tip - Every time you install a new package with `pip` under that environment, activate the environment again. - - This makes sure that if you use a terminal program installed by that package (like `flit`), you use the one from your local environment and not any other that could be installed globally. - -### Flit - -**FastAPI** uses Flit to build, package and publish the project. - -After activating the environment as described above, install `flit`: +Make sure you have the latest pip version on your virtual environment to avoid errors on the next steps:
```console -$ pip install flit +$ python -m pip install --upgrade pip ---> 100% ```
-Now re-activate the environment to make sure you are using the `flit` you just installed (and not a global one). +!!! tip + Every time you install a new package with `pip` under that environment, activate the environment again. -And now use `flit` to install the development dependencies: + This makes sure that if you use a terminal program installed by that package, you use the one from your local environment and not any other that could be installed globally. -=== "Linux, macOS" +### pip -
+After activating the environment as described above: - ```console - $ flit install --deps develop --symlink +
- ---> 100% - ``` +```console +$ pip install -r requirements.txt -
+---> 100% +``` -=== "Windows" - - If you are on Windows, use `--pth-file` instead of `--symlink`: - -
- - ```console - $ flit install --deps develop --pth-file - - ---> 100% - ``` - -
+
It will install all the dependencies and your local FastAPI in your local environment. @@ -143,10 +121,15 @@ It will install all the dependencies and your local FastAPI in your local enviro If you create a Python file that imports and uses FastAPI, and run it with the Python from your local environment, it will use your local FastAPI source code. -And if you update that local FastAPI source code, as it is installed with `--symlink` (or `--pth-file` on Windows), when you run that Python file again, it will use the fresh version of FastAPI you just edited. +And if you update that local FastAPI source code when you run that Python file again, it will use the fresh version of FastAPI you just edited. That way, you don't have to "install" your local version to be able to test every change. +!!! note "Technical Details" + This only happens when you install using this included `requiements.txt` instead of installing `pip install fastapi` directly. + + That is because inside of the `requirements.txt` file, the local version of FastAPI is marked to be installed in "editable" mode, with the `-e` option. + ### Format There is a script that you can run that will format and clean all your code: @@ -161,7 +144,7 @@ $ bash scripts/format.sh It will also auto-sort all your imports. -For it to sort them correctly, you need to have FastAPI installed locally in your environment, with the command in the section above using `--symlink` (or `--pth-file` on Windows). +For it to sort them correctly, you need to have FastAPI installed locally in your environment, with the command in the section above using `-e`. ## Docs @@ -212,6 +195,21 @@ It will serve the documentation on `http://127.0.0.1:8008`. That way, you can edit the documentation/source files and see the changes live. +!!! tip + Alternatively, you can perform the same steps that scripts does manually. + + Go into the language directory, for the main docs in English it's at `docs/en/`: + + ```console + $ cd docs/en/ + ``` + + Then run `mkdocs` in that directory: + + ```console + $ mkdocs serve --dev-addr 8008 + ``` + #### Typer CLI (optional) The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly. @@ -262,13 +260,15 @@ Here are the steps to help with translations. Check the docs about adding a pull request review to approve it or request changes. -* Check in the issues to see if there's one coordinating translations for your language. +* Check if there's a GitHub Discussion to coordinate translations for your language. You can subscribe to it, and when there's a new pull request to review, an automatic comment will be added to the discussion. * Add a single pull request per page translated. That will make it much easier for others to review it. For the languages I don't speak, I'll wait for several others to review the translation before merging. * You can also check if there are translations for your language and add a review to them, that will help me know that the translation is correct and I can merge it. + * You could check in the GitHub Discussions for your language. + * Or you can filter the existing PRs by the ones with the label for your language, for example, for Spanish, the label is `lang-es`. * Use the same Python examples and only translate the text in the docs. You don't have to change anything for this to work. @@ -300,11 +300,24 @@ $ python ./scripts/docs.py live es
+!!! tip + Alternatively, you can perform the same steps that scripts does manually. + + Go into the language directory, for the Spanish translations it's at `docs/es/`: + + ```console + $ cd docs/es/ + ``` + + Then run `mkdocs` in that directory: + + ```console + $ mkdocs serve --dev-addr 8008 + ``` + Now you can go to http://127.0.0.1:8008 and see your changes live. -If you look at the FastAPI docs website, you will see that every language has all the pages. But some pages are not translated and have a notification about the missing translation. - -But when you run it locally like this, you will only see the pages that are already translated. +You will see that every language has all the pages. But some pages are not translated and have a notification about the missing translation. Now let's say that you want to add a translation for the section [Features](features.md){.internal-link target=_blank}. @@ -323,46 +336,6 @@ docs/es/docs/features.md !!! tip Notice that the only change in the path and file name is the language code, from `en` to `es`. -* Now open the MkDocs config file for English at: - -``` -docs/en/mkdocs.yml -``` - -* Find the place where that `docs/features.md` is located in the config file. Somewhere like: - -```YAML hl_lines="8" -site_name: FastAPI -# More stuff -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -* Open the MkDocs config file for the language you are editing, e.g.: - -``` -docs/es/mkdocs.yml -``` - -* Add it there at the exact same location it was for English, e.g.: - -```YAML hl_lines="8" -site_name: FastAPI -# More stuff -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -Make sure that if there are other entries, the new entry with your translation is exactly in the same order as in the English version. - If you go to your browser you will see that now the docs show your new section. 🎉 Now you can translate it all and see how it looks as you save the file. @@ -384,55 +357,32 @@ The next step is to run the script to generate a new translation directory: $ python ./scripts/docs.py new-lang ht Successfully initialized: docs/ht -Updating ht -Updating en ``` Now you can check in your code editor the newly created directory `docs/ht/`. +That command created a file `docs/ht/mkdocs.yml` with a simple config that inherits everything from the `en` version: + +```yaml +INHERIT: ../en/mkdocs.yml +``` + !!! tip - Create a first pull request with just this, to set up the configuration for the new language, before adding translations. + You could also simply create that file with those contents manually. - That way others can help with other pages while you work on the first one. 🚀 +That command also created a dummy file `docs/ht/index.md` for the main page, you can start by translating that one. -Start by translating the main page, `docs/ht/index.md`. +You can continue with the previous instructions for an "Existing Language" for that process. -Then you can continue with the previous instructions, for an "Existing Language". - -##### New Language not supported - -If when running the live server script you get an error about the language not being supported, something like: - -``` - raise TemplateNotFound(template) -jinja2.exceptions.TemplateNotFound: partials/language/xx.html -``` - -That means that the theme doesn't support that language (in this case, with a fake 2-letter code of `xx`). - -But don't worry, you can set the theme language to English and then translate the content of the docs. - -If you need to do that, edit the `mkdocs.yml` for your new language, it will have something like: - -```YAML hl_lines="5" -site_name: FastAPI -# More stuff -theme: - # More stuff - language: xx -``` - -Change that language from `xx` (from your language code) to `en`. - -Then you can start the live server again. +You can make the first pull request with those two files, `docs/ht/mkdocs.yml` and `docs/ht/index.md`. 🎉 #### Preview the result -When you use the script at `./scripts/docs.py` with the `live` command it only shows the files and translations available for the current language. +You can use the `./scripts/docs.py` with the `live` command to preview the results (or `mkdocs serve`). -But once you are done, you can test it all as it would look online. +Once you are done, you can also test it all as it would look online, including all the other languages. To do that, first build all the docs: @@ -442,19 +392,14 @@ To do that, first build all the docs: // Use the command "build-all", this will take a bit $ python ./scripts/docs.py build-all -Updating es -Updating en Building docs for: en Building docs for: es Successfully built docs for: es -Copying en index.md to README.md ``` -That generates all the docs at `./docs_build/` for each language. This includes adding any files with missing translations, with a note saying that "this file doesn't have a translation yet". But you don't have to do anything with that directory. - -Then it builds all those independent MkDocs sites for each language, combines them, and generates the final output at `./site/`. +This builds all those independent MkDocs sites for each language, combines them, and generates the final output at `./site/`. Then you can serve that with the command `serve`: diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css index 7d3503e492..066b51725e 100644 --- a/docs/en/docs/css/custom.css +++ b/docs/en/docs/css/custom.css @@ -4,10 +4,21 @@ display: block; } +.termy { + /* For right to left languages */ + direction: ltr; +} + .termy [data-termynal] { white-space: pre-wrap; } +a.external-link { + /* For right to left languages */ + direction: ltr; + display: inline-block; +} + a.external-link::after { /* \00A0 is a non-breaking space to make the mark be on the same line as the link @@ -94,7 +105,7 @@ a.announce-link:hover { .announce-wrapper .sponsor-badge { display: block; position: absolute; - top: -5px; + top: -10px; right: 0; font-size: 0.5rem; color: #999; @@ -118,3 +129,18 @@ a.announce-link:hover { .twitter { color: #00acee; } + +/* Right to left languages */ +code { + direction: ltr; + display: inline-block; +} + +.md-content__inner h1 { + direction: ltr !important; +} + +.illustration { + margin-top: 2em; + margin-bottom: 2em; +} diff --git a/docs/en/docs/deployment/concepts.md b/docs/en/docs/deployment/concepts.md index 22604ceeb2..77419f8b0d 100644 --- a/docs/en/docs/deployment/concepts.md +++ b/docs/en/docs/deployment/concepts.md @@ -235,7 +235,7 @@ Here are some possible combinations and strategies: * One Uvicorn **process manager** would listen on the **IP** and **port**, and it would start **multiple Uvicorn worker processes** * **Kubernetes** and other distributed **container systems** * Something in the **Kubernetes** layer would listen on the **IP** and **port**. The replication would be by having **multiple containers**, each with **one Uvicorn process** running -* **Cloud services** that handle this for your +* **Cloud services** that handle this for you * The cloud service will probably **handle replication for you**. It would possibly let you define **a process to run**, or a **container image** to use, in any case, it would most probably be **a single Uvicorn process**, and the cloud service would be in charge of replicating it. !!! tip diff --git a/docs/en/docs/deployment/deta.md b/docs/en/docs/deployment/deta.md index c0dc3336a9..229d7fd5d8 100644 --- a/docs/en/docs/deployment/deta.md +++ b/docs/en/docs/deployment/deta.md @@ -1,15 +1,22 @@ -# Deploy FastAPI on Deta +# Deploy FastAPI on Deta Space -In this section you will learn how to easily deploy a **FastAPI** application on Deta using the free plan. 🎁 +In this section you will learn how to easily deploy a **FastAPI** application on Deta Space, for free. 🎁 -It will take you about **10 minutes**. +It will take you about **10 minutes** to deploy an API that you can use. After that, you can optionally release it to anyone. + +Let's dive in. !!! info - Deta is a **FastAPI** sponsor. 🎉 + Deta is a **FastAPI** sponsor. 🎉 -## A basic **FastAPI** app +## A simple **FastAPI** app -* Create a directory for your app, for example, `./fastapideta/` and enter into it. +* To start, create an empty directory with the name of your app, for example `./fastapi-deta/`, and then navigate into it. + +```console +$ mkdir fastapi-deta +$ cd fastapi-deta +``` ### FastAPI code @@ -37,14 +44,12 @@ Now, in the same directory create a file `requirements.txt` with: ```text fastapi +uvicorn[standard] ``` -!!! tip - You don't need to install Uvicorn to deploy on Deta, although you would probably want to install it locally to test your app. - ### Directory structure -You will now have one directory `./fastapideta/` with two files: +You will now have a directory `./fastapi-deta/` with two files: ``` . @@ -52,22 +57,23 @@ You will now have one directory `./fastapideta/` with two files: └── requirements.txt ``` -## Create a free Deta account +## Create a free **Deta Space** account -Now create a free account on Deta, you just need an email and password. +Next, create a free account on Deta Space, you just need an email and password. + +You don't even need a credit card, but make sure **Developer Mode** is enabled when you sign up. -You don't even need a credit card. ## Install the CLI -Once you have your account, install the Deta CLI: +Once you have your account, install the Deta Space CLI: === "Linux, macOS"
```console - $ curl -fsSL https://get.deta.dev/cli.sh | sh + $ curl -fsSL https://get.deta.dev/space-cli.sh | sh ```
@@ -77,7 +83,7 @@ Once you have your account, install the Deta ```console - $ iwr https://get.deta.dev/cli.ps1 -useb | iex + $ iwr https://get.deta.dev/space-cli.ps1 -useb | iex ``` @@ -89,95 +95,144 @@ In a new terminal, confirm that it was correctly installed with:
```console -$ deta --help +$ space --help Deta command line interface for managing deta micros. -Complete documentation available at https://docs.deta.sh +Complete documentation available at https://deta.space/docs Usage: - deta [flags] - deta [command] + space [flags] + space [command] Available Commands: - auth Change auth settings for a deta micro - + help Help about any command + link link code to project + login login to space + new create new project + push push code for project + release create release for a project + validate validate spacefile in dir + version Space CLI version ... ```
!!! tip - If you have problems installing the CLI, check the official Deta docs. + If you have problems installing the CLI, check the official Deta Space Documentation. ## Login with the CLI -Now login to Deta from the CLI with: +In order to authenticate your CLI with Deta Space, you will need an access token. + +To obtain this token, open your Deta Space Canvas, open the **Teletype** (command bar at the bottom of the Canvas), and then click on **Settings**. From there, select **Generate Token** and copy the resulting token. + + + +Now run `space login` from the Space CLI. Upon pasting the token into the CLI prompt and pressing enter, you should see a confirmation message.
```console -$ deta login +$ space login -Please, log in from the web page. Waiting.. -Logged in successfully. +To authenticate the Space CLI with your Space account, generate a new access token in your Space settings and paste it below: + +# Enter access token (41 chars) >$ ***************************************** + +👍 Login Successful! ```
-This will open a web browser and authenticate automatically. +## Create a new project in Space -## Deploy with Deta +Now that you've authenticated with the Space CLI, use it to create a new Space Project: -Next, deploy your application with the Deta CLI: +```console +$ space new + +# What is your project's name? >$ fastapi-deta +``` + +The Space CLI will ask you to name the project, we will call ours `fastapi-deta`. + +Then, it will try to automatically detect which framework or language you are using, showing you what it finds. In our case it will identify the Python app with the following message, prompting you to confirm: + +```console +⚙️ No Spacefile found, trying to auto-detect configuration ... +👇 Deta detected the following configuration: + +Micros: +name: fastapi-deta + L src: . + L engine: python3.9 + +# Do you want to bootstrap "fastapi-deta" with this configuration? (y/n)$ y +``` + +After you confirm, your project will be created in Deta Space inside a special app called Builder. Builder is a toolbox that helps you to create and manage your apps in Deta Space. + +The CLI will also create a `Spacefile` locally in the `fastapi-deta` directory. The Spacefile is a configuration file which tells Deta Space how to run your app. The `Spacefile` for your app will be as follows: + +```yaml +v: 0 +micros: + - name: fastapi-deta + src: . + engine: python3.9 +``` + +It is a `yaml` file, and you can use it to add features like scheduled tasks or modify how your app functions, which we'll do later. To learn more, read the `Spacefile` documentation. + +!!! tip + The Space CLI will also create a hidden `.space` folder in your local directory to link your local environment with Deta Space. This folder should not be included in your version control and will automatically be added to your `.gitignore` file, if you have initialized a Git repository. + +## Define the run command in the Spacefile + +The `run` command in the Spacefile tells Space what command should be executed to start your app. In this case it would be `uvicorn main:app`. + +```diff +v: 0 +micros: + - name: fastapi-deta + src: . + engine: python3.9 ++ run: uvicorn main:app +``` + +## Deploy to Deta Space + +To get your FastAPI live in the cloud, use one more CLI command:
```console -$ deta new - -Successfully created a new micro - -// Notice the "endpoint" 🔍 - -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} - -Adding dependencies... - +$ space push ---> 100% +build complete... created revision: satyr-jvjk -Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 +✔ Successfully pushed your code and created a new Revision! +ℹ Updating your development instance with the latest Revision, it will be available on your Canvas shortly. ``` -
-You will see a JSON message similar to: - -```JSON hl_lines="4" -{ - "name": "fastapideta", - "runtime": "python3.7", - "endpoint": "https://qltnci.deta.dev", - "visor": "enabled", - "http_auth": "enabled" -} -``` +This command will package your code, upload all the necessary files to Deta Space, and run a remote build of your app, resulting in a **revision**. Whenever you run `space push` successfully, a live instance of your API is automatically updated with the latest revision. !!! tip - Your deployment will have a different `"endpoint"` URL. + You can manage your revisions by opening your project in the Builder app. The live copy of your API will be visible under the **Develop** tab in Builder. ## Check it -Now open your browser in your `endpoint` URL. In the example above it was `https://qltnci.deta.dev`, but yours will be different. +The live instance of your API will also be added automatically to your Canvas (the dashboard) on Deta Space. -You will see the JSON response from your FastAPI app: + + +Click on the new app called `fastapi-deta`, and it will open your API in a new browser tab on a URL like `https://fastapi-deta-gj7ka8.deta.app/`. + +You will get a JSON response from your FastAPI app: ```JSON { @@ -185,74 +240,152 @@ You will see the JSON response from your FastAPI app: } ``` -And now go to the `/docs` for your API, in the example above it would be `https://qltnci.deta.dev/docs`. +And now you can head over to the `/docs` of your API. For this example, it would be `https://fastapi-deta-gj7ka8.deta.app/docs`. -It will show your docs like: - - + ## Enable public access -By default, Deta will handle authentication using cookies for your account. +Deta will handle authentication for your account using cookies. By default, every app or API that you `push` or install to your Space is personal - it's only accessible to you. -But once you are ready, you can make it public with: +But you can also make your API public using the `Spacefile` from earlier. + +With a `public_routes` parameter, you can specify which paths of your API should be available to the public. + +Set your `public_routes` to `"*"` to open every route of your API to the public: + +```yaml +v: 0 +micros: + - name: fastapi-deta + src: . + engine: python3.9 + public_routes: + - "/*" +``` + +Then run `space push` again to update your live API on Deta Space. + +Once it deploys, you can share your URL with anyone and they will be able to access your API. 🚀 + +## HTTPS + +Congrats! You deployed your FastAPI app to Deta Space! 🎉 🍰 + +Also, notice that Deta Space correctly handles HTTPS for you, so you don't have to take care of that and can be sure that your users will have a secure encrypted connection. ✅ 🔒 + +## Create a release + +Space also allows you to publish your API. When you publish it, anyone else can install their own copy of your API, in their own Deta Space cloud. + +To do so, run `space release` in the Space CLI to create an **unlisted release**:
```console -$ deta auth disable +$ space release -Successfully disabled http auth +# Do you want to use the latest revision (buzzard-hczt)? (y/n)$ y + +~ Creating a Release with the latest Revision + +---> 100% + +creating release... +publishing release in edge locations.. +completed... +released: fastapi-deta-exp-msbu +https://deta.space/discovery/r/5kjhgyxewkdmtotx + + Lift off -- successfully created a new Release! + Your Release is available globally on 5 Deta Edges + Anyone can install their own copy of your app. ``` -
-Now you can share that URL with anyone and they will be able to access your API. 🚀 +This command publishes your revision as a release and gives you a link. Anyone you give this link to can install your API. -## HTTPS -Congrats! You deployed your FastAPI app to Deta! 🎉 🍰 +You can also make your app publicly discoverable by creating a **listed release** with `space release --listed` in the Space CLI: -Also, notice that Deta correctly handles HTTPS for you, so you don't have to take care of that and can be sure that your clients will have a secure encrypted connection. ✅ 🔒 +
-## Check the Visor +```console +$ space release --listed -From your docs UI (they will be in a URL like `https://qltnci.deta.dev/docs`) send a request to your *path operation* `/items/{item_id}`. +# Do you want to use the latest revision (buzzard-hczt)? (y/n)$ y -For example with ID `5`. +~ Creating a listed Release with the latest Revision ... -Now go to https://web.deta.sh. +creating release... +publishing release in edge locations.. +completed... +released: fastapi-deta-exp-msbu +https://deta.space/discovery/@user/fastapi-deta -You will see there's a section to the left called "Micros" with each of your apps. + Lift off -- successfully created a new Release! + Your Release is available globally on 5 Deta Edges + Anyone can install their own copy of your app. + Listed on Discovery for others to find! +``` +
-You will see a tab with "Details", and also a tab "Visor", go to the tab "Visor". +This will allow anyone to find and install your app via Deta Discovery. Read more about releasing your app in the docs. -In there you can inspect the recent requests sent to your app. +## Check runtime logs -You can also edit them and re-play them. +Deta Space also lets you inspect the logs of every app you build or install. - +Add some logging functionality to your app by adding a `print` statement to your `main.py` file. + +```py +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int): + print(item_id) + return {"item_id": item_id} +``` + +The code within the `read_item` function includes a print statement that will output the `item_id` that is included in the URL. Send a request to your _path operation_ `/items/{item_id}` from the docs UI (which will have a URL like `https://fastapi-deta-gj7ka8.deta.app/docs`), using an ID like `5` as an example. + +Now go to your Space's Canvas. Click on the context menu (`...`) of your live app instance, and then click on **View Logs**. Here you can view your app's logs, sorted by time. + + ## Learn more -At some point, you will probably want to store some data for your app in a way that persists through time. For that you can use Deta Base, it also has a generous **free tier**. +At some point, you will probably want to store some data for your app in a way that persists through time. For that you can use Deta Base and Deta Drive, both of which have a generous **free tier**. + +You can also read more in the Deta Space Documentation. + +!!! tip + If you have any Deta related questions, comments, or feedback, head to the Deta Discord server. -You can also read more in the Deta Docs. ## Deployment Concepts -Coming back to the concepts we discussed in [Deployments Concepts](./concepts.md){.internal-link target=_blank}, here's how each of them would be handled with Deta: +Coming back to the concepts we discussed in [Deployments Concepts](./concepts.md){.internal-link target=_blank}, here's how each of them would be handled with Deta Space: -* **HTTPS**: Handled by Deta, they will give you a subdomain and handle HTTPS automatically. -* **Running on startup**: Handled by Deta, as part of their service. -* **Restarts**: Handled by Deta, as part of their service. -* **Replication**: Handled by Deta, as part of their service. -* **Memory**: Limit predefined by Deta, you could contact them to increase it. -* **Previous steps before starting**: Not directly supported, you could make it work with their Cron system or additional scripts. +- **HTTPS**: Handled by Deta Space, they will give you a subdomain and handle HTTPS automatically. +- **Running on startup**: Handled by Deta Space, as part of their service. +- **Restarts**: Handled by Deta Space, as part of their service. +- **Replication**: Handled by Deta Space, as part of their service. +- **Authentication**: Handled by Deta Space, as part of their service. +- **Memory**: Limit predefined by Deta Space, you could contact them to increase it. +- **Previous steps before starting**: Can be configured using the `Spacefile`. !!! note - Deta is designed to make it easy (and free) to deploy simple applications quickly. + Deta Space is designed to make it easy and free to build cloud applications for yourself. Then you can optionally share them with anyone. It can simplify several use cases, but at the same time, it doesn't support others, like using external databases (apart from Deta's own NoSQL database system), custom virtual machines, etc. - You can read more details in the Deta docs to see if it's the right choice for you. + You can read more details in the Deta Space Documentation to see if it's the right choice for you. diff --git a/docs/en/docs/deployment/docker.md b/docs/en/docs/deployment/docker.md index 651b0e840b..8a542622e4 100644 --- a/docs/en/docs/deployment/docker.md +++ b/docs/en/docs/deployment/docker.md @@ -142,7 +142,7 @@ Successfully installed fastapi pydantic uvicorn * Create a `main.py` file with: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -155,7 +155,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` diff --git a/docs/en/docs/deployment/index.md b/docs/en/docs/deployment/index.md index f0fd001cd5..6c43d8abbe 100644 --- a/docs/en/docs/deployment/index.md +++ b/docs/en/docs/deployment/index.md @@ -1,4 +1,4 @@ -# Deployment - Intro +# Deployment Deploying a **FastAPI** application is relatively easy. diff --git a/docs/en/docs/external-links.md b/docs/en/docs/external-links.md index 55db555994..0c91470bc0 100644 --- a/docs/en/docs/external-links.md +++ b/docs/en/docs/external-links.md @@ -56,6 +56,15 @@ Here's an incomplete list of some of them. {% endfor %} {% endif %} +### Taiwanese + +{% if external_links %} +{% for article in external_links.articles.taiwanese %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + ## Podcasts {% if external_links %} diff --git a/docs/en/docs/fastapi-people.md b/docs/en/docs/fastapi-people.md index 0cbcb69c9a..20caaa1ee8 100644 --- a/docs/en/docs/fastapi-people.md +++ b/docs/en/docs/fastapi-people.md @@ -28,7 +28,7 @@ I'm the creator and maintainer of **FastAPI**. You can read more about that in [ These are the people that: -* [Help others with issues (questions) in GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}. +* [Help others with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank}. * [Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}. * Review Pull Requests, [especially important for translations](contributing.md#translations){.internal-link target=_blank}. @@ -36,13 +36,13 @@ A round of applause to them. 👏 🙇 ## Most active users last month -These are the users that have been [helping others the most with issues (questions) in GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} during the last month. ☕ +These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last month. ☕ {% if people %}
{% for user in people.last_month_active %} -
@{{ user.login }}
Issues replied: {{ user.count }}
+
@{{ user.login }}
Questions replied: {{ user.count }}
{% endfor %}
@@ -52,7 +52,7 @@ These are the users that have been [helping others the most with issues (questio Here are the **FastAPI Experts**. 🤓 -These are the users that have [helped others the most with issues (questions) in GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} through *all time*. +These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} through *all time*. They have proven to be experts by helping many others. ✨ @@ -60,7 +60,7 @@ They have proven to be experts by helping many others. ✨
{% for user in people.experts %} -
@{{ user.login }}
Issues replied: {{ user.count }}
+
@{{ user.login }}
Questions replied: {{ user.count }}
{% endfor %}
@@ -169,7 +169,7 @@ They are supporting my work with **FastAPI** (and others), mainly through source code here. diff --git a/docs/en/docs/features.md b/docs/en/docs/features.md index e4672d5329..98f37b5344 100644 --- a/docs/en/docs/features.md +++ b/docs/en/docs/features.md @@ -166,7 +166,7 @@ With **FastAPI** you get all of **Starlette**'s features (as FastAPI is just Sta * **WebSocket** support. * In-process background tasks. * Startup and shutdown events. -* Test client built on `requests`. +* Test client built on HTTPX. * **CORS**, GZip, Static Files, Streaming responses. * **Session and Cookie** support. * 100% test coverage. @@ -189,12 +189,10 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on * If you know Python types you know how to use Pydantic. * Plays nicely with your **IDE/linter/brain**: * Because pydantic data structures are just instances of classes you define; auto-completion, linting, mypy and your intuition should all work properly with your validated data. -* **Fast**: - * in benchmarks Pydantic is faster than all other tested libraries. * Validate **complex structures**: * Use of hierarchical Pydantic models, Python `typing`’s `List` and `Dict`, etc. * And validators allow complex data schemas to be clearly and easily defined, checked and documented as JSON Schema. * You can have deeply **nested JSON** objects and have them all validated and annotated. -* **Extendible**: +* **Extensible**: * Pydantic allows custom data types to be defined or you can extend validation with methods on a model decorated with the validator decorator. * 100% test coverage. diff --git a/docs/en/docs/help-fastapi.md b/docs/en/docs/help-fastapi.md index 8d8d708ed9..e977dba200 100644 --- a/docs/en/docs/help-fastapi.md +++ b/docs/en/docs/help-fastapi.md @@ -47,7 +47,7 @@ You can: * Follow me on **GitHub**. * See other Open Source projects I have created that could help you. * Follow me to see when I create a new Open Source project. -* Follow me on **Twitter**. +* Follow me on **Twitter** or Mastodon. * Tell me how you use FastAPI (I love to hear that). * Hear when I make announcements or release new tools. * You can also follow @fastapi on Twitter (a separate account). @@ -67,29 +67,129 @@ I love to hear about how **FastAPI** is being used, what you have liked in it, i * Vote for **FastAPI** in Slant. * Vote for **FastAPI** in AlternativeTo. +* Say you use **FastAPI** on StackShare. -## Help others with issues in GitHub +## Help others with questions in GitHub -You can see existing issues and try and help others, most of the times they are questions that you might already know the answer for. 🤓 +You can try and help others with their questions in: -If you are helping a lot of people with issues, you might become an official [FastAPI Expert](fastapi-people.md#experts){.internal-link target=_blank}. 🎉 +* GitHub Discussions +* GitHub Issues + +In many cases you might already know the answer for those questions. 🤓 + +If you are helping a lot of people with their questions, you will become an official [FastAPI Expert](fastapi-people.md#experts){.internal-link target=_blank}. 🎉 + +Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗 + +The idea is for the **FastAPI** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other. + +--- + +Here's how to help others with questions (in discussions or issues): + +### Understand the question + +* Check if you can understand what is the **purpose** and use case of the person asking. + +* Then check if the question (the vast majority are questions) is **clear**. + +* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**. + +* If you can't understand the question, ask for more **details**. + +### Reproduce the problem + +For most of the cases and most of the questions there's something related to the person's **original code**. + +In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**. + +* You can ask them to provide a minimal, reproducible, example, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better. + +* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first. + +### Suggest solutions + +* After being able to understand the question, you can give them a possible **answer**. + +* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do. + +### Ask to close + +If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸 + +* Now, if that solved their problem, you can ask them to: + + * In GitHub Discussions: mark the comment as the **answer**. + * In GitHub Issues: **close** the issue. ## Watch the GitHub repository You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right): https://github.com/tiangolo/fastapi. 👀 -If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue. +If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc. -Then you can try and help them solve those issues. +Then you can try and help them solve those questions. -## Create issues +## Ask Questions -You can create a new issue in the GitHub repository, for example to: +You can create a new question in the GitHub repository, for example to: * Ask a **question** or ask about a **problem**. * Suggest a new **feature**. -**Note**: if you create an issue, then I'm going to ask you to also help others. 😉 +**Note**: if you do it, then I'm going to ask you to also help others. 😉 + +## Review Pull Requests + +You can help me review pull requests from others. + +Again, please try your best to be kind. 🤗 + +--- + +Here's what to have in mind and how to review a pull request: + +### Understand the problem + +* First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue. + +* There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that. + +### Don't worry about style + +* Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually. + +* Also don't worry about style rules, there are already automatized tools checking that. + +And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes. + +### Check the code + +* Check and read the code, see if it makes sense, **run it locally** and see if it actually solves the problem. + +* Then **comment** saying that you did that, that's how I will know you really checked it. + +!!! info + Unfortunately, I can't simply trust PRs that just have several approvals. + + Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. 😅 + + So, it's really important that you actually read and run the code, and let me know in the comments that you did. 🤓 + +* If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well 🙈), so it's better if you can focus on the fundamental things. + +### Tests + +* Help me check that the PR has **tests**. + +* Check that the tests **fail** before the PR. 🚨 + +* Then check that the tests **pass** after the PR. ✅ + +* Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that. + +* Then also comment what you tried, that way I'll know that you checked it. 🤓 ## Create a Pull Request @@ -102,14 +202,32 @@ You can [contribute](contributing.md){.internal-link target=_blank} to the sourc * You can also help to review the translations created by others. * To propose new documentation sections. * To fix an existing issue/bug. + * Make sure to add tests. * To add a new feature. + * Make sure to add tests. + * Make sure to add documentation if it's relevant. + +## Help Maintain FastAPI + +Help me maintain **FastAPI**! 🤓 + +There's a lot of work to do, and for most of it, **YOU** can do it. + +The main tasks that you can do right now are: + +* [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above). +* [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above). + +Those two tasks are what **consume time the most**. That's the main work of maintaining FastAPI. + +If you can help me with that, **you are helping me maintain FastAPI** and making sure it keeps **advancing faster and better**. 🚀 ## Join the chat Join the 👥 Discord chat server 👥 and hang out with others in the FastAPI community. !!! tip - For questions, ask them in GitHub issues, there's a much better chance you will receive help by the [FastAPI Experts](fastapi-people.md#experts){.internal-link target=_blank}. + For questions, ask them in GitHub Discussions, there's a much better chance you will receive help by the [FastAPI Experts](fastapi-people.md#experts){.internal-link target=_blank}. Use the chat only for other general conversations. @@ -119,9 +237,9 @@ There is also the previous + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/docarray-top-banner.svg b/docs/en/docs/img/sponsors/docarray-top-banner.svg new file mode 100644 index 0000000000..b2eca35443 --- /dev/null +++ b/docs/en/docs/img/sponsors/docarray-top-banner.svg @@ -0,0 +1 @@ + diff --git a/docs/en/docs/img/sponsors/docarray.svg b/docs/en/docs/img/sponsors/docarray.svg new file mode 100644 index 0000000000..f18df247e8 --- /dev/null +++ b/docs/en/docs/img/sponsors/docarray.svg @@ -0,0 +1 @@ + diff --git a/docs/en/docs/img/sponsors/doist-banner.svg b/docs/en/docs/img/sponsors/doist-banner.svg new file mode 100644 index 0000000000..3a4d9a2954 --- /dev/null +++ b/docs/en/docs/img/sponsors/doist-banner.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/doist.svg b/docs/en/docs/img/sponsors/doist.svg new file mode 100644 index 0000000000..b55855f067 --- /dev/null +++ b/docs/en/docs/img/sponsors/doist.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/flint.png b/docs/en/docs/img/sponsors/flint.png new file mode 100644 index 0000000000..761cc334c2 Binary files /dev/null and b/docs/en/docs/img/sponsors/flint.png differ diff --git a/docs/en/docs/img/sponsors/imgwhale-banner.svg b/docs/en/docs/img/sponsors/imgwhale-banner.svg new file mode 100644 index 0000000000..db87cc4c99 --- /dev/null +++ b/docs/en/docs/img/sponsors/imgwhale-banner.svg @@ -0,0 +1,14 @@ + + + + + + + + + + ImgWhale + The ultimate solution to unlimited and forevercloud storage. + + diff --git a/docs/en/docs/img/sponsors/imgwhale.svg b/docs/en/docs/img/sponsors/imgwhale.svg new file mode 100644 index 0000000000..46aefd9305 --- /dev/null +++ b/docs/en/docs/img/sponsors/imgwhale.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + ImgWhale + + The ultimate solution to unlimited and forever cloud storage. + + The ultimate solution to unlimited and forever cloud storage. + + + + diff --git a/docs/en/docs/img/sponsors/jina-ai-banner.png b/docs/en/docs/img/sponsors/jina-ai-banner.png new file mode 100644 index 0000000000..3ac6b44adc Binary files /dev/null and b/docs/en/docs/img/sponsors/jina-ai-banner.png differ diff --git a/docs/en/docs/img/sponsors/jina-ai.png b/docs/en/docs/img/sponsors/jina-ai.png new file mode 100644 index 0000000000..d6b0bfb4ec Binary files /dev/null and b/docs/en/docs/img/sponsors/jina-ai.png differ diff --git a/docs/en/docs/img/sponsors/jina-top-banner.svg b/docs/en/docs/img/sponsors/jina-top-banner.svg new file mode 100644 index 0000000000..8b62cd6197 --- /dev/null +++ b/docs/en/docs/img/sponsors/jina-top-banner.svg @@ -0,0 +1 @@ + diff --git a/docs/en/docs/img/sponsors/jina2.svg b/docs/en/docs/img/sponsors/jina2.svg new file mode 100644 index 0000000000..6a48677baa --- /dev/null +++ b/docs/en/docs/img/sponsors/jina2.svg @@ -0,0 +1 @@ + diff --git a/docs/en/docs/img/sponsors/platform-sh-banner.png b/docs/en/docs/img/sponsors/platform-sh-banner.png new file mode 100644 index 0000000000..f9f4580fac Binary files /dev/null and b/docs/en/docs/img/sponsors/platform-sh-banner.png differ diff --git a/docs/en/docs/img/sponsors/platform-sh.png b/docs/en/docs/img/sponsors/platform-sh.png new file mode 100644 index 0000000000..fb4e07becd Binary files /dev/null and b/docs/en/docs/img/sponsors/platform-sh.png differ diff --git a/docs/en/docs/img/sponsors/powens.png b/docs/en/docs/img/sponsors/powens.png new file mode 100644 index 0000000000..07fc6fa37f Binary files /dev/null and b/docs/en/docs/img/sponsors/powens.png differ diff --git a/docs/en/docs/img/sponsors/striveworks2.png b/docs/en/docs/img/sponsors/striveworks2.png new file mode 100644 index 0000000000..bed9cb0a77 Binary files /dev/null and b/docs/en/docs/img/sponsors/striveworks2.png differ diff --git a/docs/en/docs/img/sponsors/svix.svg b/docs/en/docs/img/sponsors/svix.svg new file mode 100644 index 0000000000..845a860a22 --- /dev/null +++ b/docs/en/docs/img/sponsors/svix.svg @@ -0,0 +1,178 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index 7de1e50df5..afd6d7138f 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -8,8 +8,8 @@ Test - - Coverage + + Coverage Package version @@ -27,12 +27,11 @@ --- -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. +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. @@ -100,6 +99,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._" + +
Deon Pillsbury - Cisco (ref)
+ +--- + ## **Typer**, the FastAPI of CLIs @@ -110,7 +115,7 @@ If you are building a CLI app to be ## Requirements -Python 3.6+ +Python 3.7+ FastAPI stands on the shoulders of giants: @@ -129,7 +134,7 @@ $ pip install fastapi -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. +You will also need an ASGI server, for production such as Uvicorn or Hypercorn.
@@ -148,7 +153,7 @@ $ pip install "uvicorn[standard]" * Create a file `main.py` with: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -161,7 +166,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -171,7 +176,7 @@ def read_item(item_id: int, q: Optional[str] = None): If your code uses `async` / `await`, use `async def`: ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -184,7 +189,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -263,7 +268,7 @@ Now modify the file `main.py` to receive a body from a `PUT` request. Declare the body using standard Python types, thanks to Pydantic. ```Python hl_lines="4 9-12 25-27" -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -274,7 +279,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -283,7 +288,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -326,7 +331,7 @@ You do that with standard modern Python types. You don't have to learn a new syntax, the methods or classes of a specific library, etc. -Just standard **Python 3.6+**. +Just standard **Python 3.7+**. For example, for an `int`: @@ -425,7 +430,7 @@ For a more complete example including more features, see the Strawberry and other libraries. * Many extra features (thanks to Starlette) as: * **WebSockets** - * extremely easy tests based on `requests` and `pytest` + * extremely easy tests based on HTTPX and `pytest` * **CORS** * **Cookie Sessions** * ...and more. @@ -440,12 +445,11 @@ To understand more about it, see the section ujson - for faster JSON "parsing". * email_validator - for email validation. Used by Starlette: -* requests - Required if you want to use the `TestClient`. +* 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. diff --git a/docs/en/docs/newsletter.md b/docs/en/docs/newsletter.md index 6403f31e60..782db1353c 100644 --- a/docs/en/docs/newsletter.md +++ b/docs/en/docs/newsletter.md @@ -1,5 +1,5 @@ # FastAPI and friends newsletter - + - + diff --git a/docs/en/docs/project-generation.md b/docs/en/docs/project-generation.md index 2f3d6c1d39..8ba34fa112 100644 --- a/docs/en/docs/project-generation.md +++ b/docs/en/docs/project-generation.md @@ -1,6 +1,6 @@ # Project Generation - Template -You can use a project generator to get started, as it includes a lot of the initial set up, security, database and first API endpoints already done for you. +You can use a project generator to get started, as it includes a lot of the initial set up, security, database and some API endpoints already done for you. A project generator will always have a very opinionated setup that you should update and adapt for your own needs, but it might be a good starting point for your project. diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md index 8486ed849d..693613a36f 100644 --- a/docs/en/docs/python-types.md +++ b/docs/en/docs/python-types.md @@ -1,8 +1,8 @@ # Python Types Intro -Python has support for optional "type hints". +Python has support for optional "type hints" (also called "type annotations"). -These **"type hints"** are a special syntax that allow declaring the type of a variable. +These **"type hints"** or annotations are a special syntax that allow declaring the type of a variable. By declaring types for your variables, editors and tools can give you better support. @@ -158,13 +158,31 @@ The syntax using `typing` is **compatible** with all versions, from Python 3.6 t As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations. -If you can chose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. See some examples below. +If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. + +In all the docs there are examples compatible with each version of Python (when there's a difference). + +For example "**Python 3.6+**" means it's compatible with Python 3.6 or above (including 3.7, 3.8, 3.9, 3.10, etc). And "**Python 3.9+**" means it's compatible with Python 3.9 or above (including 3.10, etc). + +If you can use the **latest versions of Python**, use the examples for the latest version, those will have the **best and simplest syntax**, for example, "**Python 3.10+**". #### List For example, let's define a variable to be a `list` of `str`. -=== "Python 3.6 and above" +=== "Python 3.9+" + + Declare the variable, with the same colon (`:`) syntax. + + As the type, put `list`. + + As the list is a type that contains some internal types, you put them in square brackets: + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial006_py39.py!} + ``` + +=== "Python 3.6+" From `typing`, import `List` (with a capital `L`): @@ -182,18 +200,6 @@ For example, let's define a variable to be a `list` of `str`. {!> ../../../docs_src/python_types/tutorial006.py!} ``` -=== "Python 3.9 and above" - - Declare the variable, with the same colon (`:`) syntax. - - As the type, put `list`. - - As the list is a type that contains some internal types, you put them in square brackets: - - ```Python hl_lines="1" - {!> ../../../docs_src/python_types/tutorial006_py39.py!} - ``` - !!! info Those internal types in the square brackets are called "type parameters". @@ -218,18 +224,18 @@ And still, the editor knows it is a `str`, and provides support for that. You would do the same to declare `tuple`s and `set`s: -=== "Python 3.6 and above" - - ```Python hl_lines="1 4" - {!> ../../../docs_src/python_types/tutorial007.py!} - ``` - -=== "Python 3.9 and above" +=== "Python 3.9+" ```Python hl_lines="1" {!> ../../../docs_src/python_types/tutorial007_py39.py!} ``` +=== "Python 3.6+" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial007.py!} + ``` + This means: * The variable `items_t` is a `tuple` with 3 items, an `int`, another `int`, and a `str`. @@ -243,18 +249,18 @@ The first type parameter is for the keys of the `dict`. The second type parameter is for the values of the `dict`: -=== "Python 3.6 and above" - - ```Python hl_lines="1 4" - {!> ../../../docs_src/python_types/tutorial008.py!} - ``` - -=== "Python 3.9 and above" +=== "Python 3.9+" ```Python hl_lines="1" {!> ../../../docs_src/python_types/tutorial008_py39.py!} ``` +=== "Python 3.6+" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008.py!} + ``` + This means: * The variable `prices` is a `dict`: @@ -267,20 +273,20 @@ You can declare that a variable can be any of **several types**, for example, an In Python 3.6 and above (including Python 3.10) you can use the `Union` type from `typing` and put inside the square brackets the possible types to accept. -In Python 3.10 there's also an **alternative syntax** were you can put the possible types separated by a vertical bar (`|`). +In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a vertical bar (`|`). -=== "Python 3.6 and above" - - ```Python hl_lines="1 4" - {!> ../../../docs_src/python_types/tutorial008b.py!} - ``` - -=== "Python 3.10 and above" +=== "Python 3.10+" ```Python hl_lines="1" {!> ../../../docs_src/python_types/tutorial008b_py310.py!} ``` +=== "Python 3.6+" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008b.py!} + ``` + In both cases this means that `item` could be an `int` or a `str`. #### Possibly `None` @@ -299,56 +305,70 @@ Using `Optional[str]` instead of just `str` will let the editor help you detecti This also means that in Python 3.10, you can use `Something | None`: -=== "Python 3.6 and above" - - ```Python hl_lines="1 4" - {!> ../../../docs_src/python_types/tutorial009.py!} - ``` - -=== "Python 3.6 and above - alternative" - - ```Python hl_lines="1 4" - {!> ../../../docs_src/python_types/tutorial009b.py!} - ``` - -=== "Python 3.10 and above" +=== "Python 3.10+" ```Python hl_lines="1" {!> ../../../docs_src/python_types/tutorial009_py310.py!} ``` +=== "Python 3.6+" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009.py!} + ``` + +=== "Python 3.6+ alternative" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009b.py!} + ``` + +#### Using `Union` or `Optional` + +If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view: + +* 🚨 Avoid using `Optional[SomeType]` +* Instead ✨ **use `Union[SomeType, None]`** ✨. + +Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required. + +I think `Union[SomeType, None]` is more explicit about what it means. + +It's just about the words and names. But those words can affect how you and your teammates think about the code. + +As an example, let's take this function: + +```Python hl_lines="1 4" +{!../../../docs_src/python_types/tutorial009c.py!} +``` + +The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter: + +```Python +say_hi() # Oh, no, this throws an error! 😱 +``` + +The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value: + +```Python +say_hi(name=None) # This works, None is valid 🎉 +``` + +The good news is, once you are on Python 3.10 you won't have to worry about that, as you will be able to simply use `|` to define unions of types: + +```Python hl_lines="1 4" +{!../../../docs_src/python_types/tutorial009c_py310.py!} +``` + +And then you won't have to worry about names like `Optional` and `Union`. 😎 + #### Generic types These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example: -=== "Python 3.6 and above" +=== "Python 3.10+" - * `List` - * `Tuple` - * `Set` - * `Dict` - * `Union` - * `Optional` - * ...and others. - -=== "Python 3.9 and above" - - You can use the same builtin types as generics (with square brakets and types inside): - - * `list` - * `tuple` - * `set` - * `dict` - - And the same as with Python 3.6, from the `typing` module: - - * `Union` - * `Optional` - * ...and others. - -=== "Python 3.10 and above" - - You can use the same builtin types as generics (with square brakets and types inside): + You can use the same builtin types as generics (with square brackets and types inside): * `list` * `tuple` @@ -361,7 +381,32 @@ These types that take type parameters in square brackets are called **Generic ty * `Optional` (the same as with Python 3.6) * ...and others. - In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types. + In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types, that's a lot better and simpler. + +=== "Python 3.9+" + + You can use the same builtin types as generics (with square brackets and types inside): + + * `list` + * `tuple` + * `set` + * `dict` + + And the same as with Python 3.6, from the `typing` module: + + * `Union` + * `Optional` + * ...and others. + +=== "Python 3.6+" + + * `List` + * `Tuple` + * `Set` + * `Dict` + * `Union` + * `Optional` + * ...and others. ### Classes as types @@ -383,6 +428,10 @@ And then, again, you get all the editor support: +Notice that this means "`one_person` is an **instance** of the class `Person`". + +It doesn't mean "`one_person` is the **class** called `Person`". + ## Pydantic models Pydantic is a Python library to perform data validation. @@ -397,22 +446,22 @@ And you get all the editor support with that resulting object. An example from the official Pydantic docs: -=== "Python 3.6 and above" +=== "Python 3.10+" ```Python - {!> ../../../docs_src/python_types/tutorial011.py!} + {!> ../../../docs_src/python_types/tutorial011_py310.py!} ``` -=== "Python 3.9 and above" +=== "Python 3.9+" ```Python {!> ../../../docs_src/python_types/tutorial011_py39.py!} ``` -=== "Python 3.10 and above" +=== "Python 3.6+" ```Python - {!> ../../../docs_src/python_types/tutorial011_py310.py!} + {!> ../../../docs_src/python_types/tutorial011.py!} ``` !!! info @@ -422,6 +471,46 @@ An example from the official Pydantic docs: You will see a lot more of all this in practice in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. +!!! tip + Pydantic 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. + +## Type Hints with Metadata Annotations + +Python also has a feature that allows putting **additional metadata** in these type hints using `Annotated`. + +=== "Python 3.9+" + + In Python 3.9, `Annotated` is part of the standard library, so you can import it from `typing`. + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial013_py39.py!} + ``` + +=== "Python 3.6+" + + In versions below Python 3.9, you import `Annotated` from `typing_extensions`. + + It will already be installed with **FastAPI**. + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial013.py!} + ``` + +Python itself doesn't do anything with this `Annotated`. And for editors and other tools, the type is still `str`. + +But you can use this space in `Annotated` to provide **FastAPI** with additional metadata about how you want your application to behave. + +The important thing to remember is that **the first *type parameter*** you pass to `Annotated` is the **actual type**. The rest, is just metadata for other tools. + +For now, you just need to know that `Annotated` exists, and that it's standard Python. 😎 + +Later you will see how **powerful** it can be. + +!!! tip + The fact that this is **standard Python** means that you will still get the **best possible developer experience** in your editor, with the tools you use to analyze and refactor your code, etc. ✨ + + And also that your code will be very compatible with many other Python tools and libraries. 🚀 + ## Type hints in **FastAPI** **FastAPI** takes advantage of these type hints to do several things. diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5c73818fed..e55bf48c76 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,24 +2,1128 @@ ## Latest Changes +* 📝 Fix form for the FastAPI and friends newsletter. PR [#9749](https://github.com/tiangolo/fastapi/pull/9749) by [@tiangolo](https://github.com/tiangolo). +* ✨ Add Material for MkDocs Insiders features and cards. PR [#9748](https://github.com/tiangolo/fastapi/pull/9748) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Remove languages without translations. PR [#9743](https://github.com/tiangolo/fastapi/pull/9743) by [@tiangolo](https://github.com/tiangolo). +* ✨ Refactor docs for building scripts, use MkDocs hooks, simplify (remove) configs for languages. PR [#9742](https://github.com/tiangolo/fastapi/pull/9742) by [@tiangolo](https://github.com/tiangolo). +* 🔨 Add MkDocs hook that renames sections based on the first index file. PR [#9737](https://github.com/tiangolo/fastapi/pull/9737) by [@tiangolo](https://github.com/tiangolo). +* 👷 Make cron jobs run only on main repo, not on forks, to avoid error notifications from missing tokens. PR [#9735](https://github.com/tiangolo/fastapi/pull/9735) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update MkDocs for other languages. PR [#9734](https://github.com/tiangolo/fastapi/pull/9734) by [@tiangolo](https://github.com/tiangolo). +* 👷 Refactor Docs CI, run in multiple workers with a dynamic matrix to optimize speed. PR [#9732](https://github.com/tiangolo/fastapi/pull/9732) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Remove old internal GitHub Action watch-previews that is no longer needed. PR [#9730](https://github.com/tiangolo/fastapi/pull/9730) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade MkDocs and MkDocs Material. PR [#9729](https://github.com/tiangolo/fastapi/pull/9729) by [@tiangolo](https://github.com/tiangolo). +* 👷 Build and deploy docs only on docs changes. PR [#9728](https://github.com/tiangolo/fastapi/pull/9728) by [@tiangolo](https://github.com/tiangolo). + +## 0.98.0 + +### Features + +* ✨ Allow disabling `redirect_slashes` at the FastAPI app level. PR [#3432](https://github.com/tiangolo/fastapi/pull/3432) by [@cyberlis](https://github.com/cyberlis). + +### Docs + +* 📝 Update docs on Pydantic using ujson internally. PR [#5804](https://github.com/tiangolo/fastapi/pull/5804) by [@mvasilkov](https://github.com/mvasilkov). +* ✏ Rewording in `docs/en/docs/tutorial/debugging.md`. PR [#9581](https://github.com/tiangolo/fastapi/pull/9581) by [@ivan-abc](https://github.com/ivan-abc). +* 📝 Add german blog post (Domain-driven Design mit Python und FastAPI). PR [#9261](https://github.com/tiangolo/fastapi/pull/9261) by [@msander](https://github.com/msander). +* ✏️ Tweak wording in `docs/en/docs/tutorial/security/index.md`. PR [#9561](https://github.com/tiangolo/fastapi/pull/9561) by [@jyothish-mohan](https://github.com/jyothish-mohan). +* 📝 Update `Annotated` notes in `docs/en/docs/tutorial/schema-extra-example.md`. PR [#9620](https://github.com/tiangolo/fastapi/pull/9620) by [@Alexandrhub](https://github.com/Alexandrhub). +* ✏️ Fix typo `Annotation` -> `Annotated` in `docs/en/docs/tutorial/query-params-str-validations.md`. PR [#9625](https://github.com/tiangolo/fastapi/pull/9625) by [@mccricardo](https://github.com/mccricardo). +* 📝 Use in memory database for testing SQL in docs. PR [#1223](https://github.com/tiangolo/fastapi/pull/1223) by [@HarshaLaxman](https://github.com/HarshaLaxman). + +### Translations + +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/metadata.md`. PR [#9681](https://github.com/tiangolo/fastapi/pull/9681) by [@TabarakoAkula](https://github.com/TabarakoAkula). +* 🌐 Fix typo in Spanish translation for `docs/es/docs/tutorial/first-steps.md`. PR [#9571](https://github.com/tiangolo/fastapi/pull/9571) by [@lilidl-nft](https://github.com/lilidl-nft). +* 🌐 Add Russian translation for `docs/tutorial/path-operation-configuration.md`. PR [#9696](https://github.com/tiangolo/fastapi/pull/9696) by [@TabarakoAkula](https://github.com/TabarakoAkula). +* 🌐 Add Chinese translation for `docs/zh/docs/advanced/security/index.md`. PR [#9666](https://github.com/tiangolo/fastapi/pull/9666) by [@lordqyxz](https://github.com/lordqyxz). +* 🌐 Add Chinese translations for `docs/zh/docs/advanced/settings.md`. PR [#9652](https://github.com/tiangolo/fastapi/pull/9652) by [@ChoyeonChern](https://github.com/ChoyeonChern). +* 🌐 Add Chinese translations for `docs/zh/docs/advanced/websockets.md`. PR [#9651](https://github.com/tiangolo/fastapi/pull/9651) by [@ChoyeonChern](https://github.com/ChoyeonChern). +* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/testing.md`. PR [#9641](https://github.com/tiangolo/fastapi/pull/9641) by [@wdh99](https://github.com/wdh99). +* 🌐 Add Russian translation for `docs/tutorial/extra-models.md`. PR [#9619](https://github.com/tiangolo/fastapi/pull/9619) by [@ivan-abc](https://github.com/ivan-abc). +* 🌐 Add Russian translation for `docs/tutorial/cors.md`. PR [#9608](https://github.com/tiangolo/fastapi/pull/9608) by [@ivan-abc](https://github.com/ivan-abc). +* 🌐 Add Polish translation for `docs/pl/docs/features.md`. PR [#5348](https://github.com/tiangolo/fastapi/pull/5348) by [@mbroton](https://github.com/mbroton). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/body-nested-models.md`. PR [#9605](https://github.com/tiangolo/fastapi/pull/9605) by [@Alexandrhub](https://github.com/Alexandrhub). + +### Internal + +* ⬆ Bump ruff from 0.0.272 to 0.0.275. PR [#9721](https://github.com/tiangolo/fastapi/pull/9721) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Update uvicorn[standard] requirement from <0.21.0,>=0.12.0 to >=0.12.0,<0.23.0. PR [#9463](https://github.com/tiangolo/fastapi/pull/9463) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump mypy from 1.3.0 to 1.4.0. PR [#9719](https://github.com/tiangolo/fastapi/pull/9719) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Update pre-commit requirement from <3.0.0,>=2.17.0 to >=2.17.0,<4.0.0. PR [#9251](https://github.com/tiangolo/fastapi/pull/9251) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump pypa/gh-action-pypi-publish from 1.8.5 to 1.8.6. PR [#9482](https://github.com/tiangolo/fastapi/pull/9482) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ✏️ Fix tooltips for light/dark theme toggler in docs. PR [#9588](https://github.com/tiangolo/fastapi/pull/9588) by [@pankaj1707k](https://github.com/pankaj1707k). +* 🔧 Set minimal hatchling version needed to build the package. PR [#9240](https://github.com/tiangolo/fastapi/pull/9240) by [@mgorny](https://github.com/mgorny). +* 📝 Add repo link to PyPI. PR [#9559](https://github.com/tiangolo/fastapi/pull/9559) by [@JacobCoffee](https://github.com/JacobCoffee). +* ✏️ Fix typos in data for tests. PR [#4958](https://github.com/tiangolo/fastapi/pull/4958) by [@ryanrussell](https://github.com/ryanrussell). +* 🔧 Update sponsors, add Flint. PR [#9699](https://github.com/tiangolo/fastapi/pull/9699) by [@tiangolo](https://github.com/tiangolo). +* 👷 Lint in CI only once, only with one version of Python, run tests with all of them. PR [#9686](https://github.com/tiangolo/fastapi/pull/9686) by [@tiangolo](https://github.com/tiangolo). + +## 0.97.0 + +### Features + +* ✨ Add support for `dependencies` in WebSocket routes. PR [#4534](https://github.com/tiangolo/fastapi/pull/4534) by [@paulo-raca](https://github.com/paulo-raca). +* ✨ Add exception handler for `WebSocketRequestValidationError` (which also allows to override it). PR [#6030](https://github.com/tiangolo/fastapi/pull/6030) by [@kristjanvalur](https://github.com/kristjanvalur). + +### Refactors + +* ⬆️ Upgrade and fully migrate to Ruff, remove isort, includes a couple of tweaks suggested by the new version of Ruff. PR [#9660](https://github.com/tiangolo/fastapi/pull/9660) by [@tiangolo](https://github.com/tiangolo). +* ♻️ Update internal type annotations and upgrade mypy. PR [#9658](https://github.com/tiangolo/fastapi/pull/9658) by [@tiangolo](https://github.com/tiangolo). +* ♻️ Simplify `AsyncExitStackMiddleware` as without Python 3.6 `AsyncExitStack` is always available. PR [#9657](https://github.com/tiangolo/fastapi/pull/9657) by [@tiangolo](https://github.com/tiangolo). + +### Upgrades + +* ⬆️ Upgrade Black. PR [#9661](https://github.com/tiangolo/fastapi/pull/9661) by [@tiangolo](https://github.com/tiangolo). + +### Internal + +* 💚 Update CI cache to fix installs when dependencies change. PR [#9659](https://github.com/tiangolo/fastapi/pull/9659) by [@tiangolo](https://github.com/tiangolo). +* ⬇️ Separate requirements for development into their own requirements.txt files, they shouldn't be extras. PR [#9655](https://github.com/tiangolo/fastapi/pull/9655) by [@tiangolo](https://github.com/tiangolo). + +## 0.96.1 + +### Fixes + +* 🐛 Fix `HTTPException` header type annotations. PR [#9648](https://github.com/tiangolo/fastapi/pull/9648) by [@tiangolo](https://github.com/tiangolo). +* 🐛 Fix OpenAPI model fields int validations, `gte` to `ge`. PR [#9635](https://github.com/tiangolo/fastapi/pull/9635) by [@tiangolo](https://github.com/tiangolo). + +### Upgrades + +* 📌 Update minimum version of Pydantic to >=1.7.4. This fixes an issue when trying to use an old version of Pydantic. PR [#9567](https://github.com/tiangolo/fastapi/pull/9567) by [@Kludex](https://github.com/Kludex). + +### Refactors + +* ♻ Remove `media_type` from `ORJSONResponse` as it's inherited from the parent class. PR [#5805](https://github.com/tiangolo/fastapi/pull/5805) by [@Kludex](https://github.com/Kludex). +* ♻ Instantiate `HTTPException` only when needed, optimization refactor. PR [#5356](https://github.com/tiangolo/fastapi/pull/5356) by [@pawamoy](https://github.com/pawamoy). + +### Docs + +* 🔥 Remove link to Pydantic's benchmark, as it was removed there. PR [#5811](https://github.com/tiangolo/fastapi/pull/5811) by [@Kludex](https://github.com/Kludex). + +### Translations + +* 🌐 Fix spelling in Indonesian translation of `docs/id/docs/tutorial/index.md`. PR [#5635](https://github.com/tiangolo/fastapi/pull/5635) by [@purwowd](https://github.com/purwowd). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/index.md`. PR [#5896](https://github.com/tiangolo/fastapi/pull/5896) by [@Wilidon](https://github.com/Wilidon). +* 🌐 Add Chinese translations for `docs/zh/docs/advanced/response-change-status-code.md` and `docs/zh/docs/advanced/response-headers.md`. PR [#9544](https://github.com/tiangolo/fastapi/pull/9544) by [@ChoyeonChern](https://github.com/ChoyeonChern). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/schema-extra-example.md`. PR [#9621](https://github.com/tiangolo/fastapi/pull/9621) by [@Alexandrhub](https://github.com/Alexandrhub). + +### Internal + +* 🔧 Add sponsor Platform.sh. PR [#9650](https://github.com/tiangolo/fastapi/pull/9650) by [@tiangolo](https://github.com/tiangolo). +* 👷 Add custom token to Smokeshow and Preview Docs for download-artifact, to prevent API rate limits. PR [#9646](https://github.com/tiangolo/fastapi/pull/9646) by [@tiangolo](https://github.com/tiangolo). +* 👷 Add custom tokens for GitHub Actions to avoid rate limits. PR [#9647](https://github.com/tiangolo/fastapi/pull/9647) by [@tiangolo](https://github.com/tiangolo). + +## 0.96.0 + +### Features + +* ⚡ Update `create_cloned_field` to use a global cache and improve startup performance. PR [#4645](https://github.com/tiangolo/fastapi/pull/4645) by [@madkinsz](https://github.com/madkinsz) and previous original PR by [@huonw](https://github.com/huonw). + +### Docs + +* 📝 Update Deta deployment tutorial for compatibility with Deta Space. PR [#6004](https://github.com/tiangolo/fastapi/pull/6004) by [@mikBighne98](https://github.com/mikBighne98). +* ✏️ Fix typo in Deta deployment tutorial. PR [#9501](https://github.com/tiangolo/fastapi/pull/9501) by [@lemonyte](https://github.com/lemonyte). + +### Translations + +* 🌐 Add Russian translation for `docs/tutorial/body.md`. PR [#3885](https://github.com/tiangolo/fastapi/pull/3885) by [@solomein-sv](https://github.com/solomein-sv). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/static-files.md`. PR [#9580](https://github.com/tiangolo/fastapi/pull/9580) by [@Alexandrhub](https://github.com/Alexandrhub). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/query-params.md`. PR [#9584](https://github.com/tiangolo/fastapi/pull/9584) by [@Alexandrhub](https://github.com/Alexandrhub). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/first-steps.md`. PR [#9471](https://github.com/tiangolo/fastapi/pull/9471) by [@AGolicyn](https://github.com/AGolicyn). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/debugging.md`. PR [#9579](https://github.com/tiangolo/fastapi/pull/9579) by [@Alexandrhub](https://github.com/Alexandrhub). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/path-params.md`. PR [#9519](https://github.com/tiangolo/fastapi/pull/9519) by [@AGolicyn](https://github.com/AGolicyn). +* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/static-files.md`. PR [#9436](https://github.com/tiangolo/fastapi/pull/9436) by [@wdh99](https://github.com/wdh99). +* 🌐 Update Spanish translation including new illustrations in `docs/es/docs/async.md`. PR [#9483](https://github.com/tiangolo/fastapi/pull/9483) by [@andresbermeoq](https://github.com/andresbermeoq). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/path-params-numeric-validations.md`. PR [#9563](https://github.com/tiangolo/fastapi/pull/9563) by [@ivan-abc](https://github.com/ivan-abc). +* 🌐 Add Russian translation for `docs/ru/docs/deployment/concepts.md`. PR [#9577](https://github.com/tiangolo/fastapi/pull/9577) by [@Xewus](https://github.com/Xewus). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/body-multiple-params.md`. PR [#9586](https://github.com/tiangolo/fastapi/pull/9586) by [@Alexandrhub](https://github.com/Alexandrhub). + +### Internal + +* 👥 Update FastAPI People. PR [#9602](https://github.com/tiangolo/fastapi/pull/9602) by [@github-actions[bot]](https://github.com/apps/github-actions). +* 🔧 Update sponsors, remove InvestSuite. PR [#9612](https://github.com/tiangolo/fastapi/pull/9612) by [@tiangolo](https://github.com/tiangolo). + +## 0.95.2 + +* ⬆️ Upgrade Starlette version to `>=0.27.0` for a security release. PR [#9541](https://github.com/tiangolo/fastapi/pull/9541) by [@tiangolo](https://github.com/tiangolo). Details on [Starlette's security advisory](https://github.com/encode/starlette/security/advisories/GHSA-v5gw-mw7f-84px). + +### Translations + +* 🌐 Add Portuguese translation for `docs/pt/docs/advanced/events.md`. PR [#9326](https://github.com/tiangolo/fastapi/pull/9326) by [@oandersonmagalhaes](https://github.com/oandersonmagalhaes). +* 🌐 Add Russian translation for `docs/ru/docs/deployment/manually.md`. PR [#9417](https://github.com/tiangolo/fastapi/pull/9417) by [@Xewus](https://github.com/Xewus). +* 🌐 Add setup for translations to Lao. PR [#9396](https://github.com/tiangolo/fastapi/pull/9396) by [@TheBrown](https://github.com/TheBrown). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/testing.md`. PR [#9403](https://github.com/tiangolo/fastapi/pull/9403) by [@Xewus](https://github.com/Xewus). +* 🌐 Add Russian translation for `docs/ru/docs/deployment/https.md`. PR [#9428](https://github.com/tiangolo/fastapi/pull/9428) by [@Xewus](https://github.com/Xewus). +* ✏ Fix command to install requirements in Windows. PR [#9445](https://github.com/tiangolo/fastapi/pull/9445) by [@MariiaRomanuik](https://github.com/MariiaRomanuik). +* 🌐 Add French translation for `docs/fr/docs/advanced/response-directly.md`. PR [#9415](https://github.com/tiangolo/fastapi/pull/9415) by [@axel584](https://github.com/axel584). +* 🌐 Initiate Czech translation setup. PR [#9288](https://github.com/tiangolo/fastapi/pull/9288) by [@3p1463k](https://github.com/3p1463k). +* ✏ Fix typo in Portuguese docs for `docs/pt/docs/index.md`. PR [#9337](https://github.com/tiangolo/fastapi/pull/9337) by [@lucasbalieiro](https://github.com/lucasbalieiro). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/response-status-code.md`. PR [#9370](https://github.com/tiangolo/fastapi/pull/9370) by [@nadia3373](https://github.com/nadia3373). + +### Internal + +* 🐛 Fix `flask.escape` warning for internal tests. PR [#9468](https://github.com/tiangolo/fastapi/pull/9468) by [@samuelcolvin](https://github.com/samuelcolvin). +* ✅ Refactor 2 tests, for consistency and simplification. PR [#9504](https://github.com/tiangolo/fastapi/pull/9504) by [@tiangolo](https://github.com/tiangolo). +* ✅ Refactor OpenAPI tests, prepare for Pydantic v2. PR [#9503](https://github.com/tiangolo/fastapi/pull/9503) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump dawidd6/action-download-artifact from 2.26.0 to 2.27.0. PR [#9394](https://github.com/tiangolo/fastapi/pull/9394) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 💚 Disable setup-python pip cache in CI. PR [#9438](https://github.com/tiangolo/fastapi/pull/9438) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump pypa/gh-action-pypi-publish from 1.6.4 to 1.8.5. PR [#9346](https://github.com/tiangolo/fastapi/pull/9346) by [@dependabot[bot]](https://github.com/apps/dependabot). + +## 0.95.1 + +### Fixes + +* 🐛 Fix using `Annotated` in routers or path operations decorated multiple times. PR [#9315](https://github.com/tiangolo/fastapi/pull/9315) by [@sharonyogev](https://github.com/sharonyogev). + +### Docs + +* 🌐 🔠 📄 🐢 Translate docs to Emoji 🥳 🎉 💥 🤯 🤯. PR [#5385](https://github.com/tiangolo/fastapi/pull/5385) by [@LeeeeT](https://github.com/LeeeeT). +* 📝 Add notification message warning about old versions of FastAPI not supporting `Annotated`. PR [#9298](https://github.com/tiangolo/fastapi/pull/9298) by [@grdworkin](https://github.com/grdworkin). +* 📝 Fix typo in `docs/en/docs/advanced/behind-a-proxy.md`. PR [#5681](https://github.com/tiangolo/fastapi/pull/5681) by [@Leommjr](https://github.com/Leommjr). +* ✏ Fix wrong import from typing module in Persian translations for `docs/fa/docs/index.md`. PR [#6083](https://github.com/tiangolo/fastapi/pull/6083) by [@Kimiaattaei](https://github.com/Kimiaattaei). +* ✏️ Fix format, remove unnecessary asterisks in `docs/en/docs/help-fastapi.md`. PR [#9249](https://github.com/tiangolo/fastapi/pull/9249) by [@armgabrielyan](https://github.com/armgabrielyan). +* ✏ Fix typo in `docs/en/docs/tutorial/query-params-str-validations.md`. PR [#9272](https://github.com/tiangolo/fastapi/pull/9272) by [@nicornk](https://github.com/nicornk). +* ✏ Fix typo/bug in inline code example in `docs/en/docs/tutorial/query-params-str-validations.md`. PR [#9273](https://github.com/tiangolo/fastapi/pull/9273) by [@tim-habitat](https://github.com/tim-habitat). +* ✏ Fix typo in `docs/en/docs/tutorial/path-params-numeric-validations.md`. PR [#9282](https://github.com/tiangolo/fastapi/pull/9282) by [@aadarsh977](https://github.com/aadarsh977). +* ✏ Fix typo: 'wll' to 'will' in `docs/en/docs/tutorial/query-params-str-validations.md`. PR [#9380](https://github.com/tiangolo/fastapi/pull/9380) by [@dasstyxx](https://github.com/dasstyxx). + +### Translations + +* 🌐 Add French translation for `docs/fr/docs/advanced/index.md`. PR [#5673](https://github.com/tiangolo/fastapi/pull/5673) by [@axel584](https://github.com/axel584). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/body-nested-models.md`. PR [#4053](https://github.com/tiangolo/fastapi/pull/4053) by [@luccasmmg](https://github.com/luccasmmg). +* 🌐 Add Russian translation for `docs/ru/docs/alternatives.md`. PR [#5994](https://github.com/tiangolo/fastapi/pull/5994) by [@Xewus](https://github.com/Xewus). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/extra-models.md`. PR [#5912](https://github.com/tiangolo/fastapi/pull/5912) by [@LorhanSohaky](https://github.com/LorhanSohaky). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/path-operation-configuration.md`. PR [#5936](https://github.com/tiangolo/fastapi/pull/5936) by [@LorhanSohaky](https://github.com/LorhanSohaky). +* 🌐 Add Russian translation for `docs/ru/docs/contributing.md`. PR [#6002](https://github.com/tiangolo/fastapi/pull/6002) by [@stigsanek](https://github.com/stigsanek). +* 🌐 Add Korean translation for `docs/tutorial/dependencies/classes-as-dependencies.md`. PR [#9176](https://github.com/tiangolo/fastapi/pull/9176) by [@sehwan505](https://github.com/sehwan505). +* 🌐 Add Russian translation for `docs/ru/docs/project-generation.md`. PR [#9243](https://github.com/tiangolo/fastapi/pull/9243) by [@Xewus](https://github.com/Xewus). +* 🌐 Add French translation for `docs/fr/docs/index.md`. PR [#9265](https://github.com/tiangolo/fastapi/pull/9265) by [@frabc](https://github.com/frabc). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/query-params-str-validations.md`. PR [#9267](https://github.com/tiangolo/fastapi/pull/9267) by [@dedkot01](https://github.com/dedkot01). +* 🌐 Add Russian translation for `docs/ru/docs/benchmarks.md`. PR [#9271](https://github.com/tiangolo/fastapi/pull/9271) by [@Xewus](https://github.com/Xewus). + +### Internal + +* 🔧 Update sponsors: remove Jina. PR [#9388](https://github.com/tiangolo/fastapi/pull/9388) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update sponsors, add databento, remove Ines's course and StriveWorks. PR [#9351](https://github.com/tiangolo/fastapi/pull/9351) by [@tiangolo](https://github.com/tiangolo). + +## 0.95.0 + +### Highlights + +This release adds support for dependencies and parameters using `Annotated` and recommends its usage. ✨ + +This has **several benefits**, one of the main ones is that now the parameters of your functions with `Annotated` would **not be affected** at all. + +If you call those functions in **other places in your code**, the actual **default values** will be kept, your editor will help you notice missing **required arguments**, Python will require you to pass required arguments at **runtime**, you will be able to **use the same functions** for different things and with different libraries (e.g. **Typer** will soon support `Annotated` too, then you could use the same function for an API and a CLI), etc. + +Because `Annotated` is **standard Python**, you still get all the **benefits** from editors and tools, like **autocompletion**, **inline errors**, etc. + +One of the **biggest benefits** is that now you can create `Annotated` dependencies that are then shared by multiple *path operation functions*, this will allow you to **reduce** a lot of **code duplication** in your codebase, while keeping all the support from editors and tools. + +For example, you could have code like this: + +```Python +def get_current_user(token: str): + # authenticate user + return User() + + +@app.get("/items/") +def read_items(user: User = Depends(get_current_user)): + ... + + +@app.post("/items/") +def create_item(*, user: User = Depends(get_current_user), item: Item): + ... + + +@app.get("/items/{item_id}") +def read_item(*, user: User = Depends(get_current_user), item_id: int): + ... + + +@app.delete("/items/{item_id}") +def delete_item(*, user: User = Depends(get_current_user), item_id: int): + ... +``` + +There's a bit of code duplication for the dependency: + +```Python +user: User = Depends(get_current_user) +``` + +...the bigger the codebase, the more noticeable it is. + +Now you can create an annotated dependency once, like this: + +```Python +CurrentUser = Annotated[User, Depends(get_current_user)] +``` + +And then you can reuse this `Annotated` dependency: + +```Python +CurrentUser = Annotated[User, Depends(get_current_user)] + + +@app.get("/items/") +def read_items(user: CurrentUser): + ... + + +@app.post("/items/") +def create_item(user: CurrentUser, item: Item): + ... + + +@app.get("/items/{item_id}") +def read_item(user: CurrentUser, item_id: int): + ... + + +@app.delete("/items/{item_id}") +def delete_item(user: CurrentUser, item_id: int): + ... +``` + +...and `CurrentUser` has all the typing information as `User`, so your editor will work as expected (autocompletion and everything), and **FastAPI** will be able to understand the dependency defined in `Annotated`. 😎 + +Roughly **all the docs** have been rewritten to use `Annotated` as the main way to declare **parameters** and **dependencies**. All the **examples** in the docs now include a version with `Annotated` and a version without it, for each of the specific Python versions (when there are small differences/improvements in more recent versions). There were around 23K new lines added between docs, examples, and tests. 🚀 + +The key updated docs are: + +* Python Types Intro: + * [Type Hints with Metadata Annotations](https://fastapi.tiangolo.com/python-types/#type-hints-with-metadata-annotations). +* Tutorial: + * [Query Parameters and String Validations - Additional validation](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#additional-validation) + * [Advantages of `Annotated`](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#advantages-of-annotated) + * [Path Parameters and Numeric Validations - Order the parameters as you need, tricks](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks) + * [Better with `Annotated`](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#better-with-annotated) + * [Dependencies - First Steps - Share `Annotated` dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/#share-annotated-dependencies) + +Special thanks to [@nzig](https://github.com/nzig) for the core implementation and to [@adriangb](https://github.com/adriangb) for the inspiration and idea with [Xpresso](https://github.com/adriangb/xpresso)! 🚀 + +### Features + +* ✨Add support for PEP-593 `Annotated` for specifying dependencies and parameters. PR [#4871](https://github.com/tiangolo/fastapi/pull/4871) by [@nzig](https://github.com/nzig). + +### Docs + +* 📝 Tweak tip recommending `Annotated` in docs. PR [#9270](https://github.com/tiangolo/fastapi/pull/9270) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update order of examples, latest Python version first, and simplify version tab names. PR [#9269](https://github.com/tiangolo/fastapi/pull/9269) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update all docs to use `Annotated` as the main recommendation, with new examples and tests. PR [#9268](https://github.com/tiangolo/fastapi/pull/9268) by [@tiangolo](https://github.com/tiangolo). + +## 0.94.1 + +### Fixes + +* 🎨 Fix types for lifespan, upgrade Starlette to 0.26.1. PR [#9245](https://github.com/tiangolo/fastapi/pull/9245) by [@tiangolo](https://github.com/tiangolo). + +## 0.94.0 + +### Upgrades + +* ⬆ Upgrade python-multipart to support 0.0.6. PR [#9212](https://github.com/tiangolo/fastapi/pull/9212) by [@musicinmybrain](https://github.com/musicinmybrain). +* ⬆️ Upgrade Starlette version, support new `lifespan` with state. PR [#9239](https://github.com/tiangolo/fastapi/pull/9239) by [@tiangolo](https://github.com/tiangolo). + +### Docs + +* 📝 Update Sentry link in docs. PR [#9218](https://github.com/tiangolo/fastapi/pull/9218) by [@smeubank](https://github.com/smeubank). + +### Translations + +* 🌐 Add Russian translation for `docs/ru/docs/history-design-future.md`. PR [#5986](https://github.com/tiangolo/fastapi/pull/5986) by [@Xewus](https://github.com/Xewus). + +### Internal + +* ➕ Add `pydantic` to PyPI classifiers. PR [#5914](https://github.com/tiangolo/fastapi/pull/5914) by [@yezz123](https://github.com/yezz123). +* ⬆ Bump black from 22.10.0 to 23.1.0. PR [#5953](https://github.com/tiangolo/fastapi/pull/5953) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump types-ujson from 5.6.0.0 to 5.7.0.1. PR [#6027](https://github.com/tiangolo/fastapi/pull/6027) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump dawidd6/action-download-artifact from 2.24.3 to 2.26.0. PR [#6034](https://github.com/tiangolo/fastapi/pull/6034) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5709](https://github.com/tiangolo/fastapi/pull/5709) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). + +## 0.93.0 + +### Features + +* ✨ Add support for `lifespan` async context managers (superseding `startup` and `shutdown` events). Initial PR [#2944](https://github.com/tiangolo/fastapi/pull/2944) by [@uSpike](https://github.com/uSpike). + +Now, instead of using independent `startup` and `shutdown` events, you can define that logic in a single function with `yield` decorated with `@asynccontextmanager` (an async context manager). + +For example: + +```Python +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} +``` + +**Note**: This is the recommended way going forward, instead of using `startup` and `shutdown` events. + +Read more about it in the new docs: [Advanced User Guide: Lifespan Events](https://fastapi.tiangolo.com/advanced/events/). + +### Docs + +* ✏ Fix formatting in `docs/en/docs/tutorial/metadata.md` for `ReDoc`. PR [#6005](https://github.com/tiangolo/fastapi/pull/6005) by [@eykamp](https://github.com/eykamp). + +### Translations + +* 🌐 Tamil translations - initial setup. PR [#5564](https://github.com/tiangolo/fastapi/pull/5564) by [@gusty1g](https://github.com/gusty1g). +* 🌐 Add French translation for `docs/fr/docs/advanced/path-operation-advanced-configuration.md`. PR [#9221](https://github.com/tiangolo/fastapi/pull/9221) by [@axel584](https://github.com/axel584). +* 🌐 Add French translation for `docs/tutorial/debugging.md`. PR [#9175](https://github.com/tiangolo/fastapi/pull/9175) by [@frabc](https://github.com/frabc). +* 🌐 Initiate Armenian translation setup. PR [#5844](https://github.com/tiangolo/fastapi/pull/5844) by [@har8](https://github.com/har8). +* 🌐 Add French translation for `deployment/manually.md`. PR [#3693](https://github.com/tiangolo/fastapi/pull/3693) by [@rjNemo](https://github.com/rjNemo). + +### Internal + +* 👷 Update translation bot messages. PR [#9206](https://github.com/tiangolo/fastapi/pull/9206) by [@tiangolo](https://github.com/tiangolo). +* 👷 Update translations bot to use Discussions, and notify when a PR is done. PR [#9183](https://github.com/tiangolo/fastapi/pull/9183) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update sponsors-badges. PR [#9182](https://github.com/tiangolo/fastapi/pull/9182) by [@tiangolo](https://github.com/tiangolo). +* 👥 Update FastAPI People. PR [#9181](https://github.com/tiangolo/fastapi/pull/9181) by [@github-actions[bot]](https://github.com/apps/github-actions). +* 🔊 Log GraphQL errors in FastAPI People, because it returns 200, with a payload with an error. PR [#9171](https://github.com/tiangolo/fastapi/pull/9171) by [@tiangolo](https://github.com/tiangolo). +* 💚 Fix/workaround GitHub Actions in Docker with git for FastAPI People. PR [#9169](https://github.com/tiangolo/fastapi/pull/9169) by [@tiangolo](https://github.com/tiangolo). +* ♻️ Refactor FastAPI Experts to use only discussions now that questions are migrated. PR [#9165](https://github.com/tiangolo/fastapi/pull/9165) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade analytics. PR [#6025](https://github.com/tiangolo/fastapi/pull/6025) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade and re-enable installing Typer-CLI. PR [#6008](https://github.com/tiangolo/fastapi/pull/6008) by [@tiangolo](https://github.com/tiangolo). + +## 0.92.0 + +🚨 This is a security fix. Please upgrade as soon as possible. + +### Upgrades + +* ⬆️ Upgrade Starlette to 0.25.0. PR [#5996](https://github.com/tiangolo/fastapi/pull/5996) by [@tiangolo](https://github.com/tiangolo). + * This solves a vulnerability that could allow denial of service attacks by using many small multipart fields/files (parts), consuming high CPU and memory. + * Only applications using forms (e.g. file uploads) could be affected. + * For most cases, upgrading won't have any breaking changes. + +## 0.91.0 + +### Upgrades + +* ⬆️ Upgrade Starlette version to `0.24.0` and refactor internals for compatibility. PR [#5985](https://github.com/tiangolo/fastapi/pull/5985) by [@tiangolo](https://github.com/tiangolo). + * This can solve nuanced errors when using middlewares. Before Starlette `0.24.0`, a new instance of each middleware class would be created when a new middleware was added. That normally was not a problem, unless the middleware class expected to be created only once, with only one instance, that happened in some cases. This upgrade would solve those cases (thanks [@adriangb](https://github.com/adriangb)! Starlette PR [#2017](https://github.com/encode/starlette/pull/2017)). Now the middleware class instances are created once, right before the first request (the first time the app is called). + * If you depended on that previous behavior, you might need to update your code. As always, make sure your tests pass before merging the upgrade. + +## 0.90.1 + +### Upgrades + +* ⬆️ Upgrade Starlette range to allow 0.23.1. PR [#5980](https://github.com/tiangolo/fastapi/pull/5980) by [@tiangolo](https://github.com/tiangolo). + +### Docs + +* ✏ Tweak wording to clarify `docs/en/docs/project-generation.md`. PR [#5930](https://github.com/tiangolo/fastapi/pull/5930) by [@chandra-deb](https://github.com/chandra-deb). +* ✏ Update Pydantic GitHub URLs. PR [#5952](https://github.com/tiangolo/fastapi/pull/5952) by [@yezz123](https://github.com/yezz123). +* 📝 Add opinion from Cisco. PR [#5981](https://github.com/tiangolo/fastapi/pull/5981) by [@tiangolo](https://github.com/tiangolo). + +### Translations + +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/cookie-params.md`. PR [#5890](https://github.com/tiangolo/fastapi/pull/5890) by [@bnzone](https://github.com/bnzone). + +### Internal + +* ✏ Update `zip-docs.sh` internal script, remove extra space. PR [#5931](https://github.com/tiangolo/fastapi/pull/5931) by [@JuanPerdomo00](https://github.com/JuanPerdomo00). + +## 0.90.0 + +### Upgrades + +* ⬆️ Bump Starlette from 0.22.0 to 0.23.0. Initial PR [#5739](https://github.com/tiangolo/fastapi/pull/5739) by [@Kludex](https://github.com/Kludex). + +### Docs + +* 📝 Add article "Tortoise ORM / FastAPI 整合快速筆記" to External Links. PR [#5496](https://github.com/tiangolo/fastapi/pull/5496) by [@Leon0824](https://github.com/Leon0824). +* 👥 Update FastAPI People. PR [#5954](https://github.com/tiangolo/fastapi/pull/5954) by [@github-actions[bot]](https://github.com/apps/github-actions). +* 📝 Micro-tweak help docs. PR [#5960](https://github.com/tiangolo/fastapi/pull/5960) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update new issue chooser to direct to GitHub Discussions. PR [#5948](https://github.com/tiangolo/fastapi/pull/5948) by [@tiangolo](https://github.com/tiangolo). +* 📝 Recommend GitHub Discussions for questions. PR [#5944](https://github.com/tiangolo/fastapi/pull/5944) by [@tiangolo](https://github.com/tiangolo). + +### Translations + +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/body-fields.md`. PR [#5898](https://github.com/tiangolo/fastapi/pull/5898) by [@simatheone](https://github.com/simatheone). +* 🌐 Add Russian translation for `docs/ru/docs/help-fastapi.md`. PR [#5970](https://github.com/tiangolo/fastapi/pull/5970) by [@tiangolo](https://github.com/tiangolo). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/static-files.md`. PR [#5858](https://github.com/tiangolo/fastapi/pull/5858) by [@batlopes](https://github.com/batlopes). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/encoder.md`. PR [#5525](https://github.com/tiangolo/fastapi/pull/5525) by [@felipebpl](https://github.com/felipebpl). +* 🌐 Add Russian translation for `docs/ru/docs/contributing.md`. PR [#5870](https://github.com/tiangolo/fastapi/pull/5870) by [@Xewus](https://github.com/Xewus). + +### Internal + +* ⬆️ Upgrade Ubuntu version for docs workflow. PR [#5971](https://github.com/tiangolo/fastapi/pull/5971) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update sponsors badges. PR [#5943](https://github.com/tiangolo/fastapi/pull/5943) by [@tiangolo](https://github.com/tiangolo). +* ✨ Compute FastAPI Experts including GitHub Discussions. PR [#5941](https://github.com/tiangolo/fastapi/pull/5941) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade isort and update pre-commit. PR [#5940](https://github.com/tiangolo/fastapi/pull/5940) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Add template for questions in Discussions. PR [#5920](https://github.com/tiangolo/fastapi/pull/5920) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update Sponsor Budget Insight to Powens. PR [#5916](https://github.com/tiangolo/fastapi/pull/5916) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update GitHub Sponsors badge data. PR [#5915](https://github.com/tiangolo/fastapi/pull/5915) by [@tiangolo](https://github.com/tiangolo). + +## 0.89.1 + +### Fixes + +* 🐛 Ignore Response classes on return annotation. PR [#5855](https://github.com/tiangolo/fastapi/pull/5855) by [@Kludex](https://github.com/Kludex). See the new docs in the PR below. + +### Docs + +* 📝 Update docs and examples for Response Model with Return Type Annotations, and update runtime error. PR [#5873](https://github.com/tiangolo/fastapi/pull/5873) by [@tiangolo](https://github.com/tiangolo). New docs at [Response Model - Return Type: Other Return Type Annotations](https://fastapi.tiangolo.com/tutorial/response-model/#other-return-type-annotations). +* 📝 Add External Link: FastAPI lambda container: serverless simplified. PR [#5784](https://github.com/tiangolo/fastapi/pull/5784) by [@rafrasenberg](https://github.com/rafrasenberg). + +### Translations + +* 🌐 Add Turkish translation for `docs/tr/docs/tutorial/first_steps.md`. PR [#5691](https://github.com/tiangolo/fastapi/pull/5691) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi). + +## 0.89.0 + +### Features + +* ✨ Add support for function return type annotations to declare the `response_model`. Initial PR [#1436](https://github.com/tiangolo/fastapi/pull/1436) by [@uriyyo](https://github.com/uriyyo). + +Now you can declare the return type / `response_model` in the function return type annotation: + +```python +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + + +@app.get("/items/") +async def read_items() -> list[Item]: + return [ + Item(name="Portal Gun", price=42.0), + Item(name="Plumbus", price=32.0), + ] +``` + +FastAPI will use the return type annotation to perform: + +* Data validation +* Automatic documentation + * It could power automatic client generators +* **Data filtering** + +Before this version it was only supported via the `response_model` parameter. + +Read more about it in the new docs: [Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/). + +### Docs + +* 📝 Add External Link: Authorization on FastAPI with Casbin. PR [#5712](https://github.com/tiangolo/fastapi/pull/5712) by [@Xhy-5000](https://github.com/Xhy-5000). +* ✏ Fix typo in `docs/en/docs/async.md`. PR [#5785](https://github.com/tiangolo/fastapi/pull/5785) by [@Kingdageek](https://github.com/Kingdageek). +* ✏ Fix typo in `docs/en/docs/deployment/concepts.md`. PR [#5824](https://github.com/tiangolo/fastapi/pull/5824) by [@kelbyfaessler](https://github.com/kelbyfaessler). + +### Translations + +* 🌐 Add Russian translation for `docs/ru/docs/fastapi-people.md`. PR [#5577](https://github.com/tiangolo/fastapi/pull/5577) by [@Xewus](https://github.com/Xewus). +* 🌐 Fix typo in Chinese translation for `docs/zh/docs/benchmarks.md`. PR [#4269](https://github.com/tiangolo/fastapi/pull/4269) by [@15027668g](https://github.com/15027668g). +* 🌐 Add Korean translation for `docs/tutorial/cors.md`. PR [#3764](https://github.com/tiangolo/fastapi/pull/3764) by [@NinaHwang](https://github.com/NinaHwang). + +### Internal + +* ⬆ Update coverage[toml] requirement from <7.0,>=6.5.0 to >=6.5.0,<8.0. PR [#5801](https://github.com/tiangolo/fastapi/pull/5801) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Update uvicorn[standard] requirement from <0.19.0,>=0.12.0 to >=0.12.0,<0.21.0 for development. PR [#5795](https://github.com/tiangolo/fastapi/pull/5795) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump dawidd6/action-download-artifact from 2.24.2 to 2.24.3. PR [#5842](https://github.com/tiangolo/fastapi/pull/5842) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👥 Update FastAPI People. PR [#5825](https://github.com/tiangolo/fastapi/pull/5825) by [@github-actions[bot]](https://github.com/apps/github-actions). +* ⬆ Bump types-ujson from 5.5.0 to 5.6.0.0. PR [#5735](https://github.com/tiangolo/fastapi/pull/5735) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump pypa/gh-action-pypi-publish from 1.5.2 to 1.6.4. PR [#5750](https://github.com/tiangolo/fastapi/pull/5750) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Add GitHub Action gate/check. PR [#5492](https://github.com/tiangolo/fastapi/pull/5492) by [@webknjaz](https://github.com/webknjaz). +* 🔧 Update sponsors, add Svix. PR [#5848](https://github.com/tiangolo/fastapi/pull/5848) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Remove Doist sponsor. PR [#5847](https://github.com/tiangolo/fastapi/pull/5847) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Update sqlalchemy requirement from <=1.4.41,>=1.3.18 to >=1.3.18,<1.4.43. PR [#5540](https://github.com/tiangolo/fastapi/pull/5540) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump nwtgck/actions-netlify from 1.2.4 to 2.0.0. PR [#5757](https://github.com/tiangolo/fastapi/pull/5757) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Refactor CI artifact upload/download for docs previews. PR [#5793](https://github.com/tiangolo/fastapi/pull/5793) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump pypa/gh-action-pypi-publish from 1.5.1 to 1.5.2. PR [#5714](https://github.com/tiangolo/fastapi/pull/5714) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👥 Update FastAPI People. PR [#5722](https://github.com/tiangolo/fastapi/pull/5722) by [@github-actions[bot]](https://github.com/apps/github-actions). +* 🔧 Update sponsors, disable course bundle. PR [#5713](https://github.com/tiangolo/fastapi/pull/5713) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Update typer[all] requirement from <0.7.0,>=0.6.1 to >=0.6.1,<0.8.0. PR [#5639](https://github.com/tiangolo/fastapi/pull/5639) by [@dependabot[bot]](https://github.com/apps/dependabot). + +## 0.88.0 + +### Upgrades + +* ⬆ Bump Starlette to version `0.22.0` to fix bad encoding for query parameters in new `TestClient`. PR [#5659](https://github.com/tiangolo/fastapi/pull/5659) by [@azogue](https://github.com/azogue). + +### Docs + +* ✏️ Fix typo in docs for `docs/en/docs/advanced/middleware.md`. PR [#5376](https://github.com/tiangolo/fastapi/pull/5376) by [@rifatrakib](https://github.com/rifatrakib). + +### Translations + +* 🌐 Add Portuguese translation for `docs/pt/docs/deployment/docker.md`. PR [#5663](https://github.com/tiangolo/fastapi/pull/5663) by [@ayr-ton](https://github.com/ayr-ton). + +### Internal + +* 👷 Tweak build-docs to improve CI performance. PR [#5699](https://github.com/tiangolo/fastapi/pull/5699) by [@tiangolo](https://github.com/tiangolo). +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5566](https://github.com/tiangolo/fastapi/pull/5566) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). +* ⬆️ Upgrade Ruff. PR [#5698](https://github.com/tiangolo/fastapi/pull/5698) by [@tiangolo](https://github.com/tiangolo). +* 👷 Remove pip cache for Smokeshow as it depends on a requirements.txt. PR [#5700](https://github.com/tiangolo/fastapi/pull/5700) by [@tiangolo](https://github.com/tiangolo). +* 💚 Fix pip cache for Smokeshow. PR [#5697](https://github.com/tiangolo/fastapi/pull/5697) by [@tiangolo](https://github.com/tiangolo). +* 👷 Fix and tweak CI cache handling. PR [#5696](https://github.com/tiangolo/fastapi/pull/5696) by [@tiangolo](https://github.com/tiangolo). +* 👷 Update `setup-python` action in tests to use new caching feature. PR [#5680](https://github.com/tiangolo/fastapi/pull/5680) by [@madkinsz](https://github.com/madkinsz). +* ⬆ Bump black from 22.8.0 to 22.10.0. PR [#5569](https://github.com/tiangolo/fastapi/pull/5569) by [@dependabot[bot]](https://github.com/apps/dependabot). + +## 0.87.0 + +Highlights of this release: + +* [Upgraded Starlette](https://github.com/encode/starlette/releases/tag/0.21.0) + * Now the `TestClient` is based on HTTPX instead of Requests. 🚀 + * There are some possible **breaking changes** in the `TestClient` usage, but [@Kludex](https://github.com/Kludex) built [bump-testclient](https://github.com/Kludex/bump-testclient) to help you automatize migrating your tests. Make sure you are using Git and that you can undo any unnecessary changes (false positive changes, etc) before using `bump-testclient`. +* New [WebSocketException (and docs)](https://fastapi.tiangolo.com/advanced/websockets/#using-depends-and-others), re-exported from Starlette. +* Upgraded and relaxed dependencies for package extras `all` (including new Uvicorn version), when you install `"fastapi[all]"`. +* New docs about how to [**Help Maintain FastAPI**](https://fastapi.tiangolo.com/help-fastapi/#help-maintain-fastapi). + +### Features + +* ⬆️ Upgrade and relax dependencies for extras "all". PR [#5634](https://github.com/tiangolo/fastapi/pull/5634) by [@tiangolo](https://github.com/tiangolo). +* ✨ Re-export Starlette's `WebSocketException` and add it to docs. PR [#5629](https://github.com/tiangolo/fastapi/pull/5629) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update references to Requests for tests to HTTPX, and add HTTPX to extras. PR [#5628](https://github.com/tiangolo/fastapi/pull/5628) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Upgrade Starlette to `0.21.0`, including the new [`TestClient` based on HTTPX](https://github.com/encode/starlette/releases/tag/0.21.0). PR [#5471](https://github.com/tiangolo/fastapi/pull/5471) by [@pawelrubin](https://github.com/pawelrubin). + +### Docs + +* ✏️ Tweak Help FastAPI from PR review after merging. PR [#5633](https://github.com/tiangolo/fastapi/pull/5633) by [@tiangolo](https://github.com/tiangolo). +* ✏️ Clarify docs on CORS. PR [#5627](https://github.com/tiangolo/fastapi/pull/5627) by [@paxcodes](https://github.com/paxcodes). +* 📝 Update Help FastAPI: Help Maintain FastAPI. PR [#5632](https://github.com/tiangolo/fastapi/pull/5632) by [@tiangolo](https://github.com/tiangolo). + +### Translations + +* 🌐 Fix highlight lines for Japanese translation for `docs/tutorial/query-params.md`. PR [#2969](https://github.com/tiangolo/fastapi/pull/2969) by [@ftnext](https://github.com/ftnext). +* 🌐 Add French translation for `docs/fr/docs/advanced/additional-status-code.md`. PR [#5477](https://github.com/tiangolo/fastapi/pull/5477) by [@axel584](https://github.com/axel584). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/request-forms-and-files.md`. PR [#5579](https://github.com/tiangolo/fastapi/pull/5579) by [@batlopes](https://github.com/batlopes). +* 🌐 Add Japanese translation for `docs/ja/docs/advanced/websockets.md`. PR [#4983](https://github.com/tiangolo/fastapi/pull/4983) by [@xryuseix](https://github.com/xryuseix). + +### Internal + +* ✨ Use Ruff for linting. PR [#5630](https://github.com/tiangolo/fastapi/pull/5630) by [@tiangolo](https://github.com/tiangolo). +* 🛠 Add Arabic issue number to Notify Translations GitHub Action. PR [#5610](https://github.com/tiangolo/fastapi/pull/5610) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump dawidd6/action-download-artifact from 2.24.1 to 2.24.2. PR [#5609](https://github.com/tiangolo/fastapi/pull/5609) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.1. PR [#5603](https://github.com/tiangolo/fastapi/pull/5603) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 📝 Update coverage badge to use Samuel Colvin's Smokeshow. PR [#5585](https://github.com/tiangolo/fastapi/pull/5585) by [@tiangolo](https://github.com/tiangolo). + +## 0.86.0 + +### Features + +* ⬆ Add Python 3.11 to the officially supported versions. PR [#5587](https://github.com/tiangolo/fastapi/pull/5587) by [@tiangolo](https://github.com/tiangolo). +* ✅ Enable tests for Python 3.11. PR [#4881](https://github.com/tiangolo/fastapi/pull/4881) by [@tiangolo](https://github.com/tiangolo). + +### Fixes + +* 🐛 Close FormData (uploaded files) after the request is done. PR [#5465](https://github.com/tiangolo/fastapi/pull/5465) by [@adriangb](https://github.com/adriangb). + +### Docs + +* ✏ Fix typo in `docs/en/docs/tutorial/security/oauth2-jwt.md`. PR [#5584](https://github.com/tiangolo/fastapi/pull/5584) by [@vivekashok1221](https://github.com/vivekashok1221). + +### Translations + +* 🌐 Update wording in Chinese translation for `docs/zh/docs/python-types.md`. PR [#5416](https://github.com/tiangolo/fastapi/pull/5416) by [@supercaizehua](https://github.com/supercaizehua). +* 🌐 Add Russian translation for `docs/ru/docs/deployment/index.md`. PR [#5336](https://github.com/tiangolo/fastapi/pull/5336) by [@Xewus](https://github.com/Xewus). +* 🌐 Update Chinese translation for `docs/tutorial/security/oauth2-jwt.md`. PR [#3846](https://github.com/tiangolo/fastapi/pull/3846) by [@jaystone776](https://github.com/jaystone776). + +### Internal + +* 👷 Update FastAPI People to exclude bots: pre-commit-ci, dependabot. PR [#5586](https://github.com/tiangolo/fastapi/pull/5586) by [@tiangolo](https://github.com/tiangolo). +* 🎨 Format OpenAPI JSON in `test_starlette_exception.py`. PR [#5379](https://github.com/tiangolo/fastapi/pull/5379) by [@iudeen](https://github.com/iudeen). +* 👷 Switch from Codecov to Smokeshow plus pytest-cov to pure coverage for internal tests. PR [#5583](https://github.com/tiangolo/fastapi/pull/5583) by [@tiangolo](https://github.com/tiangolo). +* 👥 Update FastAPI People. PR [#5571](https://github.com/tiangolo/fastapi/pull/5571) by [@github-actions[bot]](https://github.com/apps/github-actions). + +## 0.85.2 + +### Docs + +* ✏ Fix grammar and add helpful links to dependencies in `docs/en/docs/async.md`. PR [#5432](https://github.com/tiangolo/fastapi/pull/5432) by [@pamelafox](https://github.com/pamelafox). +* ✏ Fix broken link in `alternatives.md`. PR [#5455](https://github.com/tiangolo/fastapi/pull/5455) by [@su-shubham](https://github.com/su-shubham). +* ✏ Fix typo in docs about contributing, for compatibility with `pip` in Zsh. PR [#5523](https://github.com/tiangolo/fastapi/pull/5523) by [@zhangbo2012](https://github.com/zhangbo2012). +* 📝 Fix typo in docs with examples for Python 3.10 instead of 3.9. PR [#5545](https://github.com/tiangolo/fastapi/pull/5545) by [@feliciss](https://github.com/feliciss). + +### Translations + +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/request-forms.md`. PR [#4934](https://github.com/tiangolo/fastapi/pull/4934) by [@batlopes](https://github.com/batlopes). +* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md`. PR [#4971](https://github.com/tiangolo/fastapi/pull/4971) by [@Zssaer](https://github.com/Zssaer). +* 🌐 Add French translation for `deployment/deta.md`. PR [#3692](https://github.com/tiangolo/fastapi/pull/3692) by [@rjNemo](https://github.com/rjNemo). +* 🌐 Update Chinese translation for `docs/zh/docs/tutorial/query-params-str-validations.md`. PR [#5255](https://github.com/tiangolo/fastapi/pull/5255) by [@hjlarry](https://github.com/hjlarry). +* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/sql-databases.md`. PR [#4999](https://github.com/tiangolo/fastapi/pull/4999) by [@Zssaer](https://github.com/Zssaer). +* 🌐 Add Chinese translation for `docs/zh/docs/advanced/wsgi.md`. PR [#4505](https://github.com/tiangolo/fastapi/pull/4505) by [@ASpathfinder](https://github.com/ASpathfinder). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/body-multiple-params.md`. PR [#4111](https://github.com/tiangolo/fastapi/pull/4111) by [@lbmendes](https://github.com/lbmendes). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/path-params-numeric-validations.md`. PR [#4099](https://github.com/tiangolo/fastapi/pull/4099) by [@lbmendes](https://github.com/lbmendes). +* 🌐 Add French translation for `deployment/versions.md`. PR [#3690](https://github.com/tiangolo/fastapi/pull/3690) by [@rjNemo](https://github.com/rjNemo). +* 🌐 Add French translation for `docs/fr/docs/help-fastapi.md`. PR [#2233](https://github.com/tiangolo/fastapi/pull/2233) by [@JulianMaurin](https://github.com/JulianMaurin). +* 🌐 Fix typo in Chinese translation for `docs/zh/docs/tutorial/security/first-steps.md`. PR [#5530](https://github.com/tiangolo/fastapi/pull/5530) by [@yuki1sntSnow](https://github.com/yuki1sntSnow). +* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/response-status-code.md`. PR [#4922](https://github.com/tiangolo/fastapi/pull/4922) by [@batlopes](https://github.com/batlopes). +* 🔧 Add config for Tamil translations. PR [#5563](https://github.com/tiangolo/fastapi/pull/5563) by [@tiangolo](https://github.com/tiangolo). + +### Internal + +* ⬆ Bump internal dependency mypy from 0.971 to 0.982. PR [#5541](https://github.com/tiangolo/fastapi/pull/5541) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump nwtgck/actions-netlify from 1.2.3 to 1.2.4. PR [#5507](https://github.com/tiangolo/fastapi/pull/5507) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump internal dependency types-ujson from 5.4.0 to 5.5.0. PR [#5537](https://github.com/tiangolo/fastapi/pull/5537) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump dawidd6/action-download-artifact from 2.23.0 to 2.24.0. PR [#5508](https://github.com/tiangolo/fastapi/pull/5508) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Update internal dependency pytest-cov requirement from <4.0.0,>=2.12.0 to >=2.12.0,<5.0.0. PR [#5539](https://github.com/tiangolo/fastapi/pull/5539) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5536](https://github.com/tiangolo/fastapi/pull/5536) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). +* 🐛 Fix internal Trio test warnings. PR [#5547](https://github.com/tiangolo/fastapi/pull/5547) by [@samuelcolvin](https://github.com/samuelcolvin). +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5408](https://github.com/tiangolo/fastapi/pull/5408) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). +* ⬆️ Upgrade Typer to include Rich in scripts for docs. PR [#5502](https://github.com/tiangolo/fastapi/pull/5502) by [@tiangolo](https://github.com/tiangolo). +* 🐛 Fix calling `mkdocs` for languages as a subprocess to fix/enable MkDocs Material search plugin. PR [#5501](https://github.com/tiangolo/fastapi/pull/5501) by [@tiangolo](https://github.com/tiangolo). + +## 0.85.1 + +### Fixes + +* 🐛 Fix support for strings in OpenAPI status codes: `default`, `1XX`, `2XX`, `3XX`, `4XX`, `5XX`. PR [#5187](https://github.com/tiangolo/fastapi/pull/5187) by [@JarroVGIT](https://github.com/JarroVGIT). + +### Docs + +* 📝 Add WayScript x FastAPI Tutorial to External Links section. PR [#5407](https://github.com/tiangolo/fastapi/pull/5407) by [@moneeka](https://github.com/moneeka). + +### Internal + +* 👥 Update FastAPI People. PR [#5447](https://github.com/tiangolo/fastapi/pull/5447) by [@github-actions[bot]](https://github.com/apps/github-actions). +* 🔧 Disable Material for MkDocs search plugin. PR [#5495](https://github.com/tiangolo/fastapi/pull/5495) by [@tiangolo](https://github.com/tiangolo). +* 🔇 Ignore Trio warning in tests for CI. PR [#5483](https://github.com/tiangolo/fastapi/pull/5483) by [@samuelcolvin](https://github.com/samuelcolvin). + +## 0.85.0 + +### Features + +* ⬆ Upgrade version required of Starlette from `0.19.1` to `0.20.4`. Initial PR [#4820](https://github.com/tiangolo/fastapi/pull/4820) by [@Kludex](https://github.com/Kludex). + * This includes several bug fixes in Starlette. +* ⬆️ Upgrade Uvicorn max version in public extras: all. From `>=0.12.0,<0.18.0` to `>=0.12.0,<0.19.0`. PR [#5401](https://github.com/tiangolo/fastapi/pull/5401) by [@tiangolo](https://github.com/tiangolo). + +### Internal + +* ⬆️ Upgrade dependencies for doc and dev internal extras: Typer, Uvicorn. PR [#5400](https://github.com/tiangolo/fastapi/pull/5400) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade test dependencies: Black, HTTPX, databases, types-ujson. PR [#5399](https://github.com/tiangolo/fastapi/pull/5399) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade mypy and tweak internal type annotations. PR [#5398](https://github.com/tiangolo/fastapi/pull/5398) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update test dependencies, upgrade Pytest, move dependencies from dev to test. PR [#5396](https://github.com/tiangolo/fastapi/pull/5396) by [@tiangolo](https://github.com/tiangolo). + +## 0.84.0 + +### Breaking Changes + +This version of FastAPI drops support for Python 3.6. 🔥 Please upgrade to a supported version of Python (3.7 or above), Python 3.6 reached the end-of-life a long time ago. 😅☠ + +* 🔧 Update package metadata, drop support for Python 3.6, move build internals from Flit to Hatch. PR [#5240](https://github.com/tiangolo/fastapi/pull/5240) by [@ofek](https://github.com/ofek). + +## 0.83.0 + +🚨 This is probably the last release (or one of the last releases) to support Python 3.6. 🔥 + +Python 3.6 reached the [end-of-life and is no longer supported by Python](https://www.python.org/downloads/release/python-3615/) since around a year ago. + +You hopefully updated to a supported version of Python a while ago. If you haven't, you really should. + +### Features + +* ✨ Add support in `jsonable_encoder` for include and exclude with dataclasses. PR [#4923](https://github.com/tiangolo/fastapi/pull/4923) by [@DCsunset](https://github.com/DCsunset). + +### Fixes + +* 🐛 Fix `RuntimeError` raised when `HTTPException` has a status code with no content. PR [#5365](https://github.com/tiangolo/fastapi/pull/5365) by [@iudeen](https://github.com/iudeen). +* 🐛 Fix empty reponse body when default `status_code` is empty but the a `Response` parameter with `response.status_code` is set. PR [#5360](https://github.com/tiangolo/fastapi/pull/5360) by [@tmeckel](https://github.com/tmeckel). + +### Docs + +* 📝 Update `SECURITY.md`. PR [#5377](https://github.com/tiangolo/fastapi/pull/5377) by [@Kludex](https://github.com/Kludex). + +### Internal + +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5352](https://github.com/tiangolo/fastapi/pull/5352) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). + +## 0.82.0 + +🚨 This is probably the last release (or one of the last releases) to support Python 3.6. 🔥 + +Python 3.6 reached the [end-of-life and is no longer supported by Python](https://www.python.org/downloads/release/python-3615/) since around a year ago. + +You hopefully updated to a supported version of Python a while ago. If you haven't, you really should. + +### Features + +* ✨ Export `WebSocketState` in `fastapi.websockets`. PR [#4376](https://github.com/tiangolo/fastapi/pull/4376) by [@matiuszka](https://github.com/matiuszka). +* ✨ Support Python internal description on Pydantic model's docstring. PR [#3032](https://github.com/tiangolo/fastapi/pull/3032) by [@Kludex](https://github.com/Kludex). +* ✨ Update `ORJSONResponse` to support non `str` keys and serializing Numpy arrays. PR [#3892](https://github.com/tiangolo/fastapi/pull/3892) by [@baby5](https://github.com/baby5). + +### Fixes + +* 🐛 Allow exit code for dependencies with `yield` to always execute, by removing capacity limiter for them, to e.g. allow closing DB connections without deadlocks. PR [#5122](https://github.com/tiangolo/fastapi/pull/5122) by [@adriangb](https://github.com/adriangb). +* 🐛 Fix FastAPI People GitHub Action: set HTTPX timeout for GraphQL query request. PR [#5222](https://github.com/tiangolo/fastapi/pull/5222) by [@iudeen](https://github.com/iudeen). +* 🐛 Make sure a parameter defined as required is kept required in OpenAPI even if defined as optional in another dependency. PR [#4319](https://github.com/tiangolo/fastapi/pull/4319) by [@cd17822](https://github.com/cd17822). +* 🐛 Fix support for path parameters in WebSockets. PR [#3879](https://github.com/tiangolo/fastapi/pull/3879) by [@davidbrochart](https://github.com/davidbrochart). + +### Docs + +* ✏ Update Hypercorn link, now pointing to GitHub. PR [#5346](https://github.com/tiangolo/fastapi/pull/5346) by [@baconfield](https://github.com/baconfield). +* ✏ Tweak wording in `docs/en/docs/advanced/dataclasses.md`. PR [#3698](https://github.com/tiangolo/fastapi/pull/3698) by [@pfackeldey](https://github.com/pfackeldey). +* 📝 Add note about Python 3.10 `X | Y` operator in explanation about Response Models. PR [#5307](https://github.com/tiangolo/fastapi/pull/5307) by [@MendyLanda](https://github.com/MendyLanda). +* 📝 Add link to New Relic article: "How to monitor FastAPI application performance using Python agent". PR [#5260](https://github.com/tiangolo/fastapi/pull/5260) by [@sjyothi54](https://github.com/sjyothi54). +* 📝 Update docs for `ORJSONResponse` with details about improving performance. PR [#2615](https://github.com/tiangolo/fastapi/pull/2615) by [@falkben](https://github.com/falkben). +* 📝 Add docs for creating a custom Response class. PR [#5331](https://github.com/tiangolo/fastapi/pull/5331) by [@tiangolo](https://github.com/tiangolo). +* 📝 Add tip about using alias for form data fields. PR [#5329](https://github.com/tiangolo/fastapi/pull/5329) by [@tiangolo](https://github.com/tiangolo). + +### Translations + +* 🌐 Add Russian translation for `docs/ru/docs/features.md`. PR [#5315](https://github.com/tiangolo/fastapi/pull/5315) by [@Xewus](https://github.com/Xewus). +* 🌐 Update Chinese translation for `docs/zh/docs/tutorial/request-files.md`. PR [#4529](https://github.com/tiangolo/fastapi/pull/4529) by [@ASpathfinder](https://github.com/ASpathfinder). +* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/encoder.md`. PR [#4969](https://github.com/tiangolo/fastapi/pull/4969) by [@Zssaer](https://github.com/Zssaer). +* 🌐 Fix MkDocs file line for Portuguese translation of `background-task.md`. PR [#5242](https://github.com/tiangolo/fastapi/pull/5242) by [@ComicShrimp](https://github.com/ComicShrimp). + +### Internal + +* 👥 Update FastAPI People. PR [#5347](https://github.com/tiangolo/fastapi/pull/5347) by [@github-actions[bot]](https://github.com/apps/github-actions). +* ⬆ Bump dawidd6/action-download-artifact from 2.22.0 to 2.23.0. PR [#5321](https://github.com/tiangolo/fastapi/pull/5321) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5318](https://github.com/tiangolo/fastapi/pull/5318) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). +* ✏ Fix a small code highlight line error. PR [#5256](https://github.com/tiangolo/fastapi/pull/5256) by [@hjlarry](https://github.com/hjlarry). +* ♻ Internal small refactor, move `operation_id` parameter position in delete method for consistency with the code. PR [#4474](https://github.com/tiangolo/fastapi/pull/4474) by [@hiel](https://github.com/hiel). +* 🔧 Update sponsors, disable ImgWhale. PR [#5338](https://github.com/tiangolo/fastapi/pull/5338) by [@tiangolo](https://github.com/tiangolo). + +## 0.81.0 + +### Features + +* ✨ Add ReDoc `
diff --git a/docs/es/docs/advanced/additional-status-codes.md b/docs/es/docs/advanced/additional-status-codes.md index 67224fb364..1f28ea85b7 100644 --- a/docs/es/docs/advanced/additional-status-codes.md +++ b/docs/es/docs/advanced/additional-status-codes.md @@ -14,7 +14,7 @@ Pero también quieres que acepte nuevos ítems. Cuando los ítems no existan ant Para conseguir esto importa `JSONResponse` y devuelve ahí directamente tu contenido, asignando el `status_code` que quieras: -```Python hl_lines="2 19" +```Python hl_lines="4 25" {!../../../docs_src/additional_status_codes/tutorial001.py!} ``` diff --git a/docs/es/docs/advanced/index.md b/docs/es/docs/advanced/index.md index 1bee540f2b..ba1d20b0d1 100644 --- a/docs/es/docs/advanced/index.md +++ b/docs/es/docs/advanced/index.md @@ -1,4 +1,4 @@ -# Guía de Usuario Avanzada - Introducción +# Guía de Usuario Avanzada ## Características Adicionales diff --git a/docs/es/docs/async.md b/docs/es/docs/async.md index 90fd7b3d83..83dd532ee9 100644 --- a/docs/es/docs/async.md +++ b/docs/es/docs/async.md @@ -104,24 +104,40 @@ Para entender las diferencias, imagina la siguiente historia sobre hamburguesas: Vas con la persona que te gusta 😍 a pedir comida rápida 🍔, haces cola mientras el cajero 💁 recoge los pedidos de las personas de delante tuyo. +illustration + Llega tu turno, haces tu pedido de 2 hamburguesas impresionantes para esa persona 😍 y para ti. -Pagas 💸. +illustration El cajero 💁 le dice algo al chico de la cocina 👨‍🍳 para que sepa que tiene que preparar tus hamburguesas 🍔 (a pesar de que actualmente está preparando las de los clientes anteriores). +illustration + +Pagas 💸. El cajero 💁 te da el número de tu turno. +illustration + Mientras esperas, vas con esa persona 😍 y eliges una mesa, se sientan y hablan durante un rato largo (ya que las hamburguesas son muy impresionantes y necesitan un rato para prepararse ✨🍔✨). Mientras te sientas en la mesa con esa persona 😍, esperando las hamburguesas 🍔, puedes disfrutar ese tiempo admirando lo increíble, inteligente, y bien que se ve ✨😍✨. +illustration + Mientras esperas y hablas con esa persona 😍, de vez en cuando, verificas el número del mostrador para ver si ya es tu turno. Al final, en algún momento, llega tu turno. Vas al mostrador, coges tus hamburguesas 🍔 y vuelves a la mesa. +illustration + Tú y esa persona 😍 se comen las hamburguesas 🍔 y la pasan genial ✨. +illustration + +!!! info + Las ilustraciones fueron creados por Ketrina Thompson. 🎨 + --- Imagina que eres el sistema / programa 🤖 en esa historia. @@ -150,26 +166,41 @@ Haces la cola mientras varios cajeros (digamos 8) que a la vez son cocineros Todos los que están antes de ti están esperando 🕙 que sus hamburguesas 🍔 estén listas antes de dejar el mostrador porque cada uno de los 8 cajeros prepara la hamburguesa de inmediato antes de recibir el siguiente pedido. +illustration + Entonces finalmente es tu turno, haces tu pedido de 2 hamburguesas 🍔 impresionantes para esa persona 😍 y para ti. Pagas 💸. +illustration + El cajero va a la cocina 👨‍🍳. Esperas, de pie frente al mostrador 🕙, para que nadie más recoja tus hamburguesas 🍔, ya que no hay números para los turnos. +illustration + Como tu y esa persona 😍 están ocupados en impedir que alguien se ponga delante y recoja tus hamburguesas apenas llegan 🕙, tampoco puedes prestarle atención a esa persona 😞. Este es un trabajo "síncrono", estás "sincronizado" con el cajero / cocinero 👨‍🍳. Tienes que esperar y estar allí en el momento exacto en que el cajero / cocinero 👨‍🍳 termina las hamburguesas 🍔 y te las da, o de lo contrario, alguien más podría cogerlas. +illustration + Luego, el cajero / cocinero 👨‍🍳 finalmente regresa con tus hamburguesas 🍔, después de mucho tiempo esperando 🕙 frente al mostrador. +illustration + Cojes tus hamburguesas 🍔 y vas a la mesa con esa persona 😍. Sólo las comes y listo 🍔 ⏹. +illustration + No has hablado ni coqueteado mucho, ya que has pasado la mayor parte del tiempo esperando 🕙 frente al mostrador 😞. +!!! info + Las ilustraciones fueron creados por Ketrina Thompson. 🎨 + --- En este escenario de las hamburguesas paralelas, tú eres un sistema / programa 🤖 con dos procesadores (tú y la persona que te gusta 😍), ambos esperando 🕙 y dedicando su atención ⏯ a estar "esperando en el mostrador" 🕙 durante mucho tiempo. @@ -240,7 +271,7 @@ Pero en este caso, si pudieras traer a los 8 ex cajeros / cocineros / ahora limp En este escenario, cada uno de los limpiadores (incluido tú) sería un procesador, haciendo su parte del trabajo. -Y como la mayor parte del tiempo de ejecución lo coge el trabajo real (en lugar de esperar), y el trabajo en un sistema lo realiza una CPU , a estos problemas se les llama "CPU bond". +Y como la mayor parte del tiempo de ejecución lo coge el trabajo real (en lugar de esperar), y el trabajo en un sistema lo realiza una CPU , a estos problemas se les llama "CPU bound". --- @@ -257,7 +288,7 @@ Por ejemplo: Con **FastAPI** puedes aprovechar la concurrencia que es muy común para el desarrollo web (atractivo principal de NodeJS). -Pero también puedes aprovechar los beneficios del paralelismo y el multiprocesamiento (tener múltiples procesos ejecutándose en paralelo) para cargas de trabajo **CPU bond** como las de los sistemas de Machine Learning. +Pero también puedes aprovechar los beneficios del paralelismo y el multiprocesamiento (tener múltiples procesos ejecutándose en paralelo) para cargas de trabajo **CPU bound** como las de los sistemas de Machine Learning. Eso, más el simple hecho de que Python es el lenguaje principal para **Data Science**, Machine Learning y especialmente Deep Learning, hacen de FastAPI una muy buena combinación para las API y aplicaciones web de Data Science / Machine Learning (entre muchas otras). diff --git a/docs/es/docs/features.md b/docs/es/docs/features.md index 3c59eb88c0..5d6b6509a7 100644 --- a/docs/es/docs/features.md +++ b/docs/es/docs/features.md @@ -167,7 +167,7 @@ Con **FastAPI** obtienes todas las características de **Starlette** (porque Fas * Soporte para **GraphQL**. * Tareas en background. * Eventos de startup y shutdown. -* Cliente de pruebas construido con `requests`. +* Cliente de pruebas construido con HTTPX. * **CORS**, GZip, Static Files, Streaming responses. * Soporte para **Session and Cookie**. * Cobertura de pruebas al 100%. diff --git a/docs/es/docs/index.md b/docs/es/docs/index.md index 1fa79fdde0..5b75880c02 100644 --- a/docs/es/docs/index.md +++ b/docs/es/docs/index.md @@ -106,7 +106,7 @@ Si estás construyendo un app de ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -145,7 +145,7 @@ $ pip install uvicorn[standard] ```Python from fastapi import FastAPI -from typing import Optional +from typing import Union app = FastAPI() @@ -156,7 +156,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -167,7 +167,7 @@ Si tu código usa `async` / `await`, usa `async def`: ```Python hl_lines="7 12" from fastapi import FastAPI -from typing import Optional +from typing import Union app = FastAPI() @@ -178,7 +178,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -259,7 +259,7 @@ Declara el body usando las declaraciones de tipo estándares de Python gracias a ```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel -from typing import Optional +from typing import Union app = FastAPI() @@ -267,7 +267,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -276,7 +276,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -418,7 +418,7 @@ Para un ejemplo más completo que incluye más características ve el ujson - para "parsing" de JSON más rápido. * email_validator - para validación de emails. Usados por Starlette: -* requests - Requerido si quieres usar el `TestClient`. +* httpx - Requerido si quieres usar el `TestClient`. * jinja2 - Requerido si quieres usar la configuración por defecto de templates. * python-multipart - Requerido si quieres dar soporte a "parsing" de formularios, con `request.form()`. * itsdangerous - Requerido para dar soporte a `SessionMiddleware`. diff --git a/docs/es/docs/tutorial/first-steps.md b/docs/es/docs/tutorial/first-steps.md index 110036e8ca..efa61f9944 100644 --- a/docs/es/docs/tutorial/first-steps.md +++ b/docs/es/docs/tutorial/first-steps.md @@ -181,7 +181,7 @@ $ uvicorn main:my_awesome_api --reload -### Paso 3: crea un *operación de path* +### Paso 3: crea una *operación de path* #### Path diff --git a/docs/es/docs/tutorial/index.md b/docs/es/docs/tutorial/index.md index 9cbdb27275..1cff8b4e3e 100644 --- a/docs/es/docs/tutorial/index.md +++ b/docs/es/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Tutorial - Guía de Usuario - Introducción +# Tutorial - Guía de Usuario Este tutorial te muestra cómo usar **FastAPI** con la mayoría de sus características paso a paso. @@ -41,7 +41,7 @@ Para el tutorial, es posible que quieras instalarlo con todas las dependencias y
```console -$ pip install fastapi[all] +$ pip install "fastapi[all]" ---> 100% ``` @@ -62,7 +62,7 @@ $ pip install fastapi[all] También debes instalar `uvicorn` para que funcione como tu servidor: ``` - pip install uvicorn[standard] + pip install "uvicorn[standard]" ``` Y lo mismo para cada una de las dependencias opcionales que quieras utilizar. diff --git a/docs/es/docs/tutorial/query-params.md b/docs/es/docs/tutorial/query-params.md index 69caee6e8b..482af8dc06 100644 --- a/docs/es/docs/tutorial/query-params.md +++ b/docs/es/docs/tutorial/query-params.md @@ -75,7 +75,7 @@ En este caso el parámetro de la función `q` será opcional y será `None` por !!! note "Nota" FastAPI sabrá que `q` es opcional por el `= None`. - El `Optional` en `Optional[str]` no es usado por FastAPI (FastAPI solo usará la parte `str`), pero el `Optional[str]` le permitirá a tu editor ayudarte a encontrar errores en tu código. + El `Union` en `Union[str, None]` no es usado por FastAPI (FastAPI solo usará la parte `str`), pero el `Union[str, None]` le permitirá a tu editor ayudarte a encontrar errores en tu código. ## Conversión de tipos de parámetros de query diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml index b544f9b387..de18856f44 100644 --- a/docs/es/mkdocs.yml +++ b/docs/es/mkdocs.yml @@ -1,147 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/es/ -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: es -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- python-types.md -- Tutorial - Guía de Usuario: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md -- Guía de Usuario Avanzada: - - advanced/index.md -- async.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md index 0070de1796..2480843891 100644 --- a/docs/fa/docs/index.md +++ b/docs/fa/docs/index.md @@ -1,16 +1,12 @@ - -{!../../../docs/missing-translation.md!} - -

FastAPI

- FastAPI framework, high performance, easy to learn, fast to code, ready for production + فریم‌ورک FastAPI، کارایی بالا، یادگیری آسان، کدنویسی سریع، آماده برای استفاده در محیط پروداکشن

- - Test + + Test Coverage @@ -25,103 +21,99 @@ --- -**Documentation**: https://fastapi.tiangolo.com +**مستندات**: https://fastapi.tiangolo.com -**Source Code**: https://github.com/tiangolo/fastapi +**کد منبع**: https://github.com/tiangolo/fastapi --- +FastAPI یک وب فریم‌ورک مدرن و سریع (با کارایی بالا) برای ایجاد APIهای متنوع (وب، وب‌سوکت و غبره) با زبان پایتون نسخه +۳.۶ است. این فریم‌ورک با رعایت کامل راهنمای نوع داده (Type Hint) ایجاد شده است. -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. +ویژگی‌های کلیدی این فریم‌ورک عبارتند از: -The key features are: +* **سرعت**: کارایی بسیار بالا و قابل مقایسه با **NodeJS** و **Go** (با تشکر از Starlette و Pydantic). [یکی از سریع‌ترین فریم‌ورک‌های پایتونی موجود](#performance). -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). +* **کدنویسی سریع**: افزایش ۲۰۰ تا ۳۰۰ درصدی سرعت توسعه فابلیت‌های جدید. * +* **باگ کمتر**: کاهش ۴۰ درصدی خطاهای انسانی (برنامه‌نویسی). * +* **غریزی**: پشتیبانی فوق‌العاده در محیط‌های توسعه یکپارچه (IDE). تکمیل در همه بخش‌های کد. کاهش زمان رفع باگ. +* **آسان**: طراحی شده برای یادگیری و استفاده آسان. کاهش زمان مورد نیاز برای مراجعه به مستندات. +* **کوچک**: کاهش تکرار در کد. چندین قابلیت برای هر پارامتر (منظور پارامترهای ورودی تابع هندلر می‌باشد، به بخش خلاصه در همین صفحه مراجعه شود). باگ کمتر. +* **استوار**: ایجاد کدی آماده برای استفاده در محیط پروداکشن و تولید خودکار مستندات تعاملی +* **مبتنی بر استانداردها**: مبتنی بر (و منطبق با) استانداردهای متن باز مربوط به API: OpenAPI (سوگر سابق) و JSON Schema. -* **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 -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - + {% endfor %} {% endif %} -Other sponsors +دیگر اسپانسرها -## Opinions +## نظر دیگران در مورد FastAPI -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" +

[...] I'm using FastAPI a ton these days. [...] I'm actually planning to use it for all of my team's ML services at Microsoft. Some of them are getting integrated into the core Windows product and some Office products."
Kabir Khan - Microsoft (ref)
--- -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" +
"We adopted the FastAPI library to spawn a RESTserver that can be queried to obtain predictions. [for Ludwig]"
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
--- -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" +
"Netflix is pleased to announce the open-source release of our crisis management orchestration framework: Dispatch! [built with FastAPI]"
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
--- -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" +
"I’m over the moon excited about FastAPI. It’s so fun!"
Brian Okken - Python Bytes podcast host (ref)
--- -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" +
"Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted Hug to be - it's really inspiring to see someone build that."
Timothy Crosley - Hug creator (ref)
--- -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" +
"If you're looking to learn one modern framework for building REST APIs, check out FastAPI [...] It's fast, easy to use and easy to learn [...]"
-"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" +
"We've switched over to FastAPI for our APIs [...] I think you'll like it [...]"
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
--- -## **Typer**, the FastAPI of CLIs +## **Typer**, فریم‌ورکی معادل FastAPI برای کار با واسط خط فرمان -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. +اگر در حال ساختن برنامه‌ای برای استفاده در CLI (به جای استفاده در وب) هستید، می‌توانید از **Typer**. استفاده کنید. -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 +**Typer** دوقلوی کوچکتر FastAPI است و قرار است معادلی برای FastAPI در برنامه‌های CLI باشد.️ 🚀 -## Requirements +## نیازمندی‌ها -Python 3.6+ +پایتون +۳.۶ -FastAPI stands on the shoulders of giants: +FastAPI مبتنی بر ابزارهای قدرتمند زیر است: -* Starlette for the web parts. -* Pydantic for the data parts. +* فریم‌ورک Starlette برای بخش وب. +* کتابخانه Pydantic برای بخش داده‌. -## Installation +## نصب
@@ -133,7 +125,7 @@ $ pip install fastapi
-You will also need an ASGI server, for production such as Uvicorn or Hypercorn. +نصب یک سرور پروداکشن نظیر Uvicorn یا Hypercorn نیز جزء نیازمندی‌هاست.
@@ -145,14 +137,13 @@ $ pip install "uvicorn[standard]"
-## Example +## مثال -### Create it - -* Create a file `main.py` with: +### ایجاد کنید +* فایلی به نام `main.py` با محتوای زیر ایجاد کنید : ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -165,14 +156,14 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ```
-Or use async def... +همچنین می‌توانید از async def... نیز استفاده کنید -If your code uses `async` / `await`, use `async def`: +اگر در کدتان از `async` / `await` استفاده می‌کنید, از `async def` برای تعریف تابع خود استفاده کنید: ```Python hl_lines="9 14" from typing import Optional @@ -192,15 +183,16 @@ async def read_item(item_id: int, q: Optional[str] = None): return {"item_id": item_id, "q": q} ``` -**Note**: +**توجه**: + +اگر با `async / await` آشنا نیستید، به بخش _"عجله‌ دارید?"_ در صفحه درباره `async` و `await` در مستندات مراجعه کنید. -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs.
-### Run it +### اجرا کنید -Run the server with: +با استفاده از دستور زیر سرور را اجرا کنید:
@@ -217,54 +209,54 @@ INFO: Application startup complete.
-About the command uvicorn main:app --reload... +درباره دستور uvicorn main:app --reload... -The command `uvicorn main:app` refers to: +دستور `uvicorn main:app` شامل موارد زیر است: -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. +* `main`: فایل `main.py` (ماژول پایتون ایجاد شده). +* `app`: شیء ایجاد شده در فایل `main.py` در خط `app = FastAPI()`. +* `--reload`: ریستارت کردن سرور با تغییر کد. تنها در هنگام توسعه از این گزینه استفاده شود..
-### Check it +### بررسی کنید -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. +آدرس http://127.0.0.1:8000/items/5?q=somequery را در مرورگر خود باز کنید. -You will see the JSON response as: +پاسخ JSON زیر را مشاهده خواهید کرد: ```JSON {"item_id": 5, "q": "somequery"} ``` -You already created an API that: +تا اینجا شما APIای ساختید که: -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. +* درخواست‌های HTTP به _مسیرهای_ `/` و `/items/{item_id}` را دریافت می‌کند. +* هردو _مسیر_ عملیات (یا HTTP _متد_) `GET` را پشتیبانی می‌کنند. +* _مسیر_ `/items/{item_id}` شامل _پارامتر مسیر_ `item_id` از نوع `int` است. +* _مسیر_ `/items/{item_id}` شامل _پارامتر پرسمان_ اختیاری `q` از نوع `str` است. -### Interactive API docs +### مستندات API تعاملی -Now go to http://127.0.0.1:8000/docs. +حال به آدرس http://127.0.0.1:8000/docs بروید. -You will see the automatic interactive API documentation (provided by Swagger UI): +مستندات API تعاملی (ایجاد شده به کمک Swagger UI) را مشاهده خواهید کرد: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Alternative API docs +### مستندات API جایگزین -And now, go to http://127.0.0.1:8000/redoc. +حال به آدرس http://127.0.0.1:8000/redoc بروید. -You will see the alternative automatic documentation (provided by ReDoc): +مستندات خودکار دیگری را مشاهده خواهید کرد که به کمک ReDoc ایجاد می‌شود: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Example upgrade +## تغییر مثال -Now modify the file `main.py` to receive a body from a `PUT` request. +حال فایل `main.py` را مطابق زیر ویرایش کنید تا بتوانید بدنه یک درخواست `PUT` را دریافت کنید. -Declare the body using standard Python types, thanks to Pydantic. +به کمک Pydantic بدنه درخواست را با انواع استاندارد پایتون تعریف کنید. ```Python hl_lines="4 9-12 25-27" from typing import Optional @@ -278,7 +270,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -287,7 +279,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -296,173 +288,174 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). +سرور به صورت خودکار ری‌استارت می‌شود (زیرا پیشتر از گزینه `--reload` در دستور `uvicorn` استفاده کردیم). -### Interactive API docs upgrade +### تغییر مستندات API تعاملی -Now go to http://127.0.0.1:8000/docs. +مجددا به آدرس http://127.0.0.1:8000/docs بروید. -* The interactive API documentation will be automatically updated, including the new body: +* مستندات API تعاملی به صورت خودکار به‌روز شده است و شامل بدنه تعریف شده در مرحله قبل است: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: +* روی دکمه "Try it out" کلیک کنید, اکنون می‌توانید پارامترهای مورد نیاز هر API را مشخص کرده و به صورت مستقیم با آنها تعامل کنید: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: +* سپس روی دکمه "Execute" کلیک کنید, خواهید دید که واسط کاریری با APIهای تعریف شده ارتباط برقرار کرده، پارامترهای مورد نیاز را به آن‌ها ارسال می‌کند، سپس نتایج را دریافت کرده و در صفحه نشان می‌دهد: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### Alternative API docs upgrade +### تغییر مستندات API جایگزین -And now, go to http://127.0.0.1:8000/redoc. +حال به آدرس http://127.0.0.1:8000/redoc بروید. -* The alternative documentation will also reflect the new query parameter and body: +* خواهید دید که مستندات جایگزین نیز به‌روزرسانی شده و شامل پارامتر پرسمان و بدنه تعریف شده می‌باشد: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Recap +### خلاصه -In summary, you declare **once** the types of parameters, body, etc. as function parameters. +به طور خلاصه شما **یک بار** انواع پارامترها، بدنه و غیره را به عنوان پارامترهای ورودی تابع خود تعریف می‌کنید. -You do that with standard modern Python types. + این کار را با استفاده از انواع استاندارد و مدرن موجود در پایتون انجام می‌دهید. -You don't have to learn a new syntax, the methods or classes of a specific library, etc. +نیازی به یادگیری نحو جدید یا متدها و کلاس‌های یک کتابخانه بخصوص و غیره نیست. -Just standard **Python 3.6+**. +تنها **پایتون +۳.۶**. -For example, for an `int`: +به عنوان مثال برای یک پارامتر از نوع `int`: ```Python item_id: int ``` -or for a more complex `Item` model: +یا برای یک مدل پیچیده‌تر مثل `Item`: ```Python item: Item ``` -...and with that single declaration you get: +...و با همین اعلان تمامی قابلیت‌های زیر در دسترس قرار می‌گیرد: -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: +* پشتیبانی ویرایشگر متنی شامل: + * تکمیل کد. + * بررسی انواع داده. +* اعتبارسنجی داده: + * خطاهای خودکار و مشخص در هنگام نامعتبر بودن داده + * اعتبارسنجی، حتی برای اشیاء JSON تو در تو. +* تبدیل داده ورودی: که از شبکه رسیده به انواع و داد‌ه‌ پایتونی. این داده‌ شامل: * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: + * پارامترهای مسیر. + * پارامترهای پرسمان. + * کوکی‌ها. + * سرآیند‌ها (هدرها). + * فرم‌ها. + * فایل‌ها. +* تبدیل داده خروجی: تبدیل از انواع و داده‌ پایتون به داده شبکه (مانند JSON): + * تبدیل انواع داده پایتونی (`str`, `int`, `float`, `bool`, `list` و غیره). + * اشیاء `datetime`. + * اشیاء `UUID`. + * qمدل‌های پایگاه‌داده. + * و موارد بیشمار دیگر. +* دو مدل مستند API تعاملی خودکار : * Swagger UI. * ReDoc. --- -Coming back to the previous code example, **FastAPI** will: +به مثال قبلی باز می‌گردیم، در این مثال **FastAPI** موارد زیر را انجام می‌دهد: -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. +* اعتبارسنجی اینکه پارامتر `item_id` در مسیر درخواست‌های `GET` و `PUT` موجود است . +* اعتبارسنجی اینکه پارامتر `item_id` در درخواست‌های `GET` و `PUT` از نوع `int` است. + * اگر غیر از این موارد باشد، سرویس‌گیرنده خطای مفید و مشخصی دریافت خواهد کرد. +* بررسی وجود پارامتر پرسمان اختیاری `q` (مانند `http://127.0.0.1:8000/items/foo?q=somequery`) در درخواست‌های `GET`. + * از آنجا که پارامتر `q` با `= None` مقداردهی شده است, این پارامتر اختیاری است. + * اگر از مقدار اولیه `None` استفاده نکنیم، این پارامتر الزامی خواهد بود (همانند بدنه درخواست در درخواست `PUT`). +* برای درخواست‌های `PUT` به آدرس `/items/{item_id}`, بدنه درخواست باید از نوع JSON تعریف شده باشد: + * بررسی اینکه بدنه شامل فیلدی با نام `name` و از نوع `str` است. + * بررسی اینکه بدنه شامل فیلدی با نام `price` و از نوع `float` است. + * بررسی اینکه بدنه شامل فیلدی اختیاری با نام `is_offer` است, که در صورت وجود باید از نوع `bool` باشد. + * تمامی این موارد برای اشیاء JSON در هر عمقی قابل بررسی می‌باشد. +* تبدیل از/به JSON به صورت خودکار. +* مستندسازی همه چیز با استفاده از OpenAPI, که می‌توان از آن برای موارد زیر استفاده کرد: + * سیستم مستندات تعاملی. + * تولید خودکار کد سرویس‌گیرنده‌ در زبان‌های برنامه‌نویسی بیشمار. +* فراهم سازی ۲ مستند تعاملی مبتنی بر وب به صورت پیش‌فرض . --- -We just scratched the surface, but you already get the idea of how it all works. +موارد ذکر شده تنها پاره‌ای از ویژگی‌های بیشمار FastAPI است اما ایده‌ای کلی از طرز کار آن در اختیار قرار می‌دهد. -Try changing the line with: +خط زیر را به این صورت تغییر دهید: ```Python return {"item_name": item.name, "item_id": item_id} ``` -...from: +از: ```Python ... "item_name": item.name ... ``` -...to: +به: ```Python ... "item_price": item.price ... ``` -...and see how your editor will auto-complete the attributes and know their types: +در حین تایپ کردن توجه کنید که چگونه ویرایش‌گر، ویژگی‌های کلاس `Item` را تشخیص داده و به تکمیل خودکار آنها کمک می‌کند: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -For a more complete example including more features, see the Tutorial - User Guide. +برای مشاهده مثال‌های کامل‌تر که شامل قابلیت‌های بیشتری از FastAPI باشد به بخش آموزش - راهنمای کاربر مراجعه کنید. -**Spoiler alert**: the tutorial - user guide includes: +**هشدار اسپویل**: بخش آموزش - راهنمای کاربر شامل موارد زیر است: -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with Strawberry and other libraries. -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on `requests` and `pytest` +* اعلان **پارامترهای** موجود در بخش‌های دیگر درخواست، شامل: **سرآیند‌ (هدر)ها**, **کوکی‌ها**, **فیلد‌های فرم** و **فایل‌ها**. +* چگونگی تنظیم **محدودیت‌های اعتبارسنجی** به عنوان مثال `maximum_length` یا `regex`. +* سیستم **Dependency Injection** قوی و کاربردی. +* امنیت و تایید هویت, شامل پشتیبانی از **OAuth2** مبتنی بر **JWT tokens** و **HTTP Basic**. +* تکنیک پیشرفته برای تعریف **مدل‌های چند سطحی JSON** (بر اساس Pydantic). +* قابلیت‌های اضافی دیگر (بر اساس Starlette) شامل: + * **وب‌سوکت** + * **GraphQL** + * تست‌های خودکار آسان مبتنی بر HTTPX و `pytest` * **CORS** * **Cookie Sessions** - * ...and more. + * و موارد بیشمار دیگر. -## Performance +## کارایی -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) +معیار (بنچمارک‌)های مستقل TechEmpower حاکی از آن است که برنامه‌های **FastAPI** که تحت Uvicorn اجرا می‌شود، یکی از سریع‌ترین فریم‌ورک‌های مبتنی بر پایتون, است که کمی ضعیف‌تر از Starlette و Uvicorn عمل می‌کند (فریم‌ورک و سروری که FastAPI بر اساس آنها ایجاد شده است) (*) -To understand more about it, see the section Benchmarks. +برای درک بهتری از این موضوع به بخش بنچ‌مارک‌ها مراجعه کنید. -## Optional Dependencies +## نیازمندی‌های اختیاری -Used by Pydantic: +استفاده شده توسط Pydantic: -* ujson - for faster JSON "parsing". -* email_validator - for email validation. +* email_validator - برای اعتبارسنجی آدرس‌های ایمیل. -Used by Starlette: +استفاده شده توسط Starlette: -* requests - 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`. +* HTTPX - در صورتی که می‌خواهید از `TestClient` استفاده کنید. +* aiofiles - در صورتی که می‌خواهید از `FileResponse` و `StaticFiles` استفاده کنید. +* jinja2 - در صورتی که بخواهید از پیکربندی پیش‌فرض برای قالب‌ها استفاده کنید. +* python-multipart - در صورتی که بخواهید با استفاده از `request.form()` از قابلیت "تجزیه (parse)" فرم استفاده کنید. +* itsdangerous - در صورتی که بخواید از `SessionMiddleware` پشتیبانی کنید. +* pyyaml - برای پشتیبانی `SchemaGenerator` در Starlet (به احتمال زیاد برای کار کردن با FastAPI به آن نیازی پیدا نمی‌کنید.). +* graphene - در صورتی که از `GraphQLApp` پشتیبانی می‌کنید. +* ujson - در صورتی که بخواهید از `UJSONResponse` استفاده کنید. -Used by FastAPI / Starlette: +استفاده شده توسط FastAPI / Starlette: -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. +* uvicorn - برای سرور اجرا کننده برنامه وب. +* orjson - در صورتی که بخواهید از `ORJSONResponse` استفاده کنید. -You can install all of these with `pip install "fastapi[all]"`. +می‌توان همه این موارد را با استفاده از دستور `pip install fastapi[all]`. به صورت یکجا نصب کرد. -## License +## لایسنس -This project is licensed under the terms of the MIT license. +این پروژه مشمول قوانین و مقررات لایسنس MIT است. diff --git a/docs/fa/mkdocs.yml b/docs/fa/mkdocs.yml index 3966a60261..de18856f44 100644 --- a/docs/fa/mkdocs.yml +++ b/docs/fa/mkdocs.yml @@ -1,137 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/fa/ -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: fa -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - 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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/fr/docs/advanced/additional-responses.md b/docs/fr/docs/advanced/additional-responses.md new file mode 100644 index 0000000000..35b57594d5 --- /dev/null +++ b/docs/fr/docs/advanced/additional-responses.md @@ -0,0 +1,240 @@ +# Réponses supplémentaires dans OpenAPI + +!!! Attention + Ceci concerne un sujet plutôt avancé. + + Si vous débutez avec **FastAPI**, vous n'en aurez peut-être pas besoin. + +Vous pouvez déclarer des réponses supplémentaires, avec des codes HTTP, des types de médias, des descriptions, etc. + +Ces réponses supplémentaires seront incluses dans le schéma OpenAPI, elles apparaîtront donc également dans la documentation de l'API. + +Mais pour ces réponses supplémentaires, vous devez vous assurer de renvoyer directement une `Response` comme `JSONResponse`, avec votre code HTTP et votre contenu. + +## Réponse supplémentaire avec `model` + +Vous pouvez ajouter à votre décorateur de *paramètre de chemin* un paramètre `responses`. + +Il prend comme valeur un `dict` dont les clés sont des codes HTTP pour chaque réponse, comme `200`, et la valeur de ces clés sont d'autres `dict` avec des informations pour chacun d'eux. + +Chacun de ces `dict` de réponse peut avoir une clé `model`, contenant un modèle Pydantic, tout comme `response_model`. + +**FastAPI** prendra ce modèle, générera son schéma JSON et l'inclura au bon endroit dans OpenAPI. + +Par exemple, pour déclarer une autre réponse avec un code HTTP `404` et un modèle Pydantic `Message`, vous pouvez écrire : + +```Python hl_lines="18 22" +{!../../../docs_src/additional_responses/tutorial001.py!} +``` + +!!! Remarque + Gardez à l'esprit que vous devez renvoyer directement `JSONResponse`. + +!!! Info + La clé `model` ne fait pas partie d'OpenAPI. + + **FastAPI** prendra le modèle Pydantic à partir de là, générera le `JSON Schema` et le placera au bon endroit. + + Le bon endroit est : + + * Dans la clé `content`, qui a pour valeur un autre objet JSON (`dict`) qui contient : + * Une clé avec le type de support, par ex. `application/json`, qui contient comme valeur un autre objet JSON, qui contient : + * Une clé `schema`, qui a pour valeur le schéma JSON du modèle, voici le bon endroit. + * **FastAPI** ajoute ici une référence aux schémas JSON globaux à un autre endroit de votre OpenAPI au lieu de l'inclure directement. De cette façon, d'autres applications et clients peuvent utiliser ces schémas JSON directement, fournir de meilleurs outils de génération de code, etc. + +Les réponses générées au format OpenAPI pour cette *opération de chemin* seront : + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Additional Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +Les schémas sont référencés à un autre endroit du modèle OpenAPI : + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## Types de médias supplémentaires pour la réponse principale + +Vous pouvez utiliser ce même paramètre `responses` pour ajouter différents types de médias pour la même réponse principale. + +Par exemple, vous pouvez ajouter un type de média supplémentaire `image/png`, en déclarant que votre *opération de chemin* peut renvoyer un objet JSON (avec le type de média `application/json`) ou une image PNG : + +```Python hl_lines="19-24 28" +{!../../../docs_src/additional_responses/tutorial002.py!} +``` + +!!! Remarque + Notez que vous devez retourner l'image en utilisant directement un `FileResponse`. + +!!! Info + À moins que vous ne spécifiiez explicitement un type de média différent dans votre paramètre `responses`, FastAPI supposera que la réponse a le même type de média que la classe de réponse principale (par défaut `application/json`). + + Mais si vous avez spécifié une classe de réponse personnalisée avec `None` comme type de média, FastAPI utilisera `application/json` pour toute réponse supplémentaire associée à un modèle. + +## Combinaison d'informations + +Vous pouvez également combiner des informations de réponse provenant de plusieurs endroits, y compris les paramètres `response_model`, `status_code` et `responses`. + +Vous pouvez déclarer un `response_model`, en utilisant le code HTTP par défaut `200` (ou un code personnalisé si vous en avez besoin), puis déclarer des informations supplémentaires pour cette même réponse dans `responses`, directement dans le schéma OpenAPI. + +**FastAPI** conservera les informations supplémentaires des `responses` et les combinera avec le schéma JSON de votre modèle. + +Par exemple, vous pouvez déclarer une réponse avec un code HTTP `404` qui utilise un modèle Pydantic et a une `description` personnalisée. + +Et une réponse avec un code HTTP `200` qui utilise votre `response_model`, mais inclut un `example` personnalisé : + +```Python hl_lines="20-31" +{!../../../docs_src/additional_responses/tutorial003.py!} +``` + +Tout sera combiné et inclus dans votre OpenAPI, et affiché dans la documentation de l'API : + + + +## Combinez les réponses prédéfinies et les réponses personnalisées + +Vous voulez peut-être avoir des réponses prédéfinies qui s'appliquent à de nombreux *paramètre de chemin*, mais vous souhaitez les combiner avec des réponses personnalisées nécessaires à chaque *opération de chemin*. + +Dans ces cas, vous pouvez utiliser la technique Python "d'affection par décomposition" (appelé _unpacking_ en anglais) d'un `dict` avec `**dict_to_unpack` : + +``` Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +Ici, `new_dict` contiendra toutes les paires clé-valeur de `old_dict` plus la nouvelle paire clé-valeur : + +``` Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +Vous pouvez utiliser cette technique pour réutiliser certaines réponses prédéfinies dans vos *paramètres de chemin* et les combiner avec des réponses personnalisées supplémentaires. + +Par exemple: + +```Python hl_lines="13-17 26" +{!../../../docs_src/additional_responses/tutorial004.py!} +``` + +## Plus d'informations sur les réponses OpenAPI + +Pour voir exactement ce que vous pouvez inclure dans les réponses, vous pouvez consulter ces sections dans la spécification OpenAPI : + +* Objet Responses de OpenAPI , il inclut le `Response Object`. +* Objet Response de OpenAPI , vous pouvez inclure n'importe quoi directement dans chaque réponse à l'intérieur de votre paramètre `responses`. Y compris `description`, `headers`, `content` (à l'intérieur de cela, vous déclarez différents types de médias et schémas JSON) et `links`. diff --git a/docs/fr/docs/advanced/additional-status-codes.md b/docs/fr/docs/advanced/additional-status-codes.md new file mode 100644 index 0000000000..e7b003707b --- /dev/null +++ b/docs/fr/docs/advanced/additional-status-codes.md @@ -0,0 +1,37 @@ +# Codes HTTP supplémentaires + +Par défaut, **FastAPI** renverra les réponses à l'aide d'une structure de données `JSONResponse`, en plaçant la réponse de votre *chemin d'accès* à l'intérieur de cette `JSONResponse`. + +Il utilisera le code HTTP par défaut ou celui que vous avez défini dans votre *chemin d'accès*. + +## Codes HTTP supplémentaires + +Si vous souhaitez renvoyer des codes HTTP supplémentaires en plus du code principal, vous pouvez le faire en renvoyant directement une `Response`, comme une `JSONResponse`, et en définissant directement le code HTTP supplémentaire. + +Par exemple, disons que vous voulez avoir un *chemin d'accès* qui permet de mettre à jour les éléments et renvoie les codes HTTP 200 "OK" en cas de succès. + +Mais vous voulez aussi qu'il accepte de nouveaux éléments. Et lorsque les éléments n'existaient pas auparavant, il les crée et renvoie un code HTTP de 201 "Créé". + +Pour y parvenir, importez `JSONResponse` et renvoyez-y directement votre contenu, en définissant le `status_code` que vous souhaitez : + +```Python hl_lines="4 25" +{!../../../docs_src/additional_status_codes/tutorial001.py!} +``` + +!!! Attention + Lorsque vous renvoyez une `Response` directement, comme dans l'exemple ci-dessus, elle sera renvoyée directement. + + Elle ne sera pas sérialisée avec un modèle. + + Assurez-vous qu'il contient les données souhaitées et que les valeurs soient dans un format JSON valides (si vous utilisez une `JSONResponse`). + +!!! note "Détails techniques" + Vous pouvez également utiliser `from starlette.responses import JSONResponse`. + + Pour plus de commodités, **FastAPI** fournit les objets `starlette.responses` sous forme d'un alias accessible par `fastapi.responses`. Mais la plupart des réponses disponibles proviennent directement de Starlette. Il en est de même avec l'objet `statut`. + +## Documents OpenAPI et API + +Si vous renvoyez directement des codes HTTP et des réponses supplémentaires, ils ne seront pas inclus dans le schéma OpenAPI (la documentation de l'API), car FastAPI n'a aucun moyen de savoir à l'avance ce que vous allez renvoyer. + +Mais vous pouvez documenter cela dans votre code, en utilisant : [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/advanced/index.md b/docs/fr/docs/advanced/index.md new file mode 100644 index 0000000000..f4fa5ecf69 --- /dev/null +++ b/docs/fr/docs/advanced/index.md @@ -0,0 +1,24 @@ +# Guide de l'utilisateur avancé + +## Caractéristiques supplémentaires + +Le [Tutoriel - Guide de l'utilisateur](../tutorial/){.internal-link target=_blank} devrait suffire à vous faire découvrir toutes les fonctionnalités principales de **FastAPI**. + +Dans les sections suivantes, vous verrez des options, configurations et fonctionnalités supplémentaires. + +!!! Note + Les sections de ce chapitre ne sont **pas nécessairement "avancées"**. + + Et il est possible que pour votre cas d'utilisation, la solution se trouve dans l'un d'entre eux. + +## Lisez d'abord le didacticiel + +Vous pouvez utiliser la plupart des fonctionnalités de **FastAPI** grâce aux connaissances du [Tutoriel - Guide de l'utilisateur](../tutorial/){.internal-link target=_blank}. + +Et les sections suivantes supposent que vous l'avez lu et que vous en connaissez les idées principales. + +## Cours TestDriven.io + +Si vous souhaitez suivre un cours pour débutants avancés pour compléter cette section de la documentation, vous pouvez consulter : Développement piloté par les tests avec FastAPI et Docker par **TestDriven.io**. + +10 % de tous les bénéfices de ce cours sont reversés au développement de **FastAPI**. 🎉 😄 diff --git a/docs/fr/docs/advanced/path-operation-advanced-configuration.md b/docs/fr/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 0000000000..ace9f19f93 --- /dev/null +++ b/docs/fr/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,168 @@ +# Configuration avancée des paramètres de chemin + +## ID d'opération OpenAPI + +!!! Attention + Si vous n'êtes pas un "expert" en OpenAPI, vous n'en avez probablement pas besoin. + +Dans OpenAPI, les chemins sont des ressources, tels que /users/ ou /items/, exposées par votre API, et les opérations sont les méthodes HTTP utilisées pour manipuler ces chemins, telles que GET, POST ou DELETE. Les operationId sont des chaînes uniques facultatives utilisées pour identifier une opération d'un chemin. Vous pouvez définir l'OpenAPI `operationId` à utiliser dans votre *opération de chemin* avec le paramètre `operation_id`. + +Vous devez vous assurer qu'il est unique pour chaque opération. + +```Python hl_lines="6" +{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} +``` + +### Utilisation du nom *path operation function* comme operationId + +Si vous souhaitez utiliser les noms de fonction de vos API comme `operationId`, vous pouvez les parcourir tous et remplacer chaque `operation_id` de l'*opération de chemin* en utilisant leur `APIRoute.name`. + +Vous devriez le faire après avoir ajouté toutes vos *paramètres de chemin*. + +```Python hl_lines="2 12-21 24" +{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} +``` + +!!! Astuce + Si vous appelez manuellement `app.openapi()`, vous devez mettre à jour les `operationId` avant. + +!!! Attention + Pour faire cela, vous devez vous assurer que chacun de vos *chemin* ait un nom unique. + + Même s'ils se trouvent dans des modules différents (fichiers Python). + +## Exclusion d'OpenAPI + +Pour exclure un *chemin* du schéma OpenAPI généré (et donc des systèmes de documentation automatiques), utilisez le paramètre `include_in_schema` et assignez-lui la valeur `False` : + +```Python hl_lines="6" +{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!} +``` + +## Description avancée de docstring + +Vous pouvez limiter le texte utilisé de la docstring d'une *fonction de chemin* qui sera affiché sur OpenAPI. + +L'ajout d'un `\f` (un caractère d'échappement "form feed") va permettre à **FastAPI** de tronquer la sortie utilisée pour OpenAPI à ce stade. + +Il n'apparaîtra pas dans la documentation, mais d'autres outils (tel que Sphinx) pourront utiliser le reste. + +```Python hl_lines="19-29" +{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} +``` + +## Réponses supplémentaires + +Vous avez probablement vu comment déclarer le `response_model` et le `status_code` pour une *opération de chemin*. + +Cela définit les métadonnées sur la réponse principale d'une *opération de chemin*. + +Vous pouvez également déclarer des réponses supplémentaires avec leurs modèles, codes de statut, etc. + +Il y a un chapitre entier ici dans la documentation à ce sujet, vous pouvez le lire sur [Réponses supplémentaires dans OpenAPI](./additional-responses.md){.internal-link target=_blank}. + +## OpenAPI supplémentaire + +Lorsque vous déclarez un *chemin* dans votre application, **FastAPI** génère automatiquement les métadonnées concernant ce *chemin* à inclure dans le schéma OpenAPI. + +!!! note "Détails techniques" + La spécification OpenAPI appelle ces métaonnées des Objets d'opération. + +Il contient toutes les informations sur le *chemin* et est utilisé pour générer automatiquement la documentation. + +Il inclut les `tags`, `parameters`, `requestBody`, `responses`, etc. + +Ce schéma OpenAPI spécifique aux *operations* est normalement généré automatiquement par **FastAPI**, mais vous pouvez également l'étendre. + +!!! Astuce + Si vous avez seulement besoin de déclarer des réponses supplémentaires, un moyen plus pratique de le faire est d'utiliser les [réponses supplémentaires dans OpenAPI](./additional-responses.md){.internal-link target=_blank}. + +Vous pouvez étendre le schéma OpenAPI pour une *opération de chemin* en utilisant le paramètre `openapi_extra`. + +### Extensions OpenAPI + +Cet `openapi_extra` peut être utile, par exemple, pour déclarer [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) : + +```Python hl_lines="6" +{!../../../docs_src/path_operation_advanced_configuration/tutorial005.py!} +``` + +Si vous ouvrez la documentation automatique de l'API, votre extension apparaîtra au bas du *chemin* spécifique. + + + +Et dans le fichier openapi généré (`/openapi.json`), vous verrez également votre extension dans le cadre du *chemin* spécifique : + +```JSON hl_lines="22" +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### Personnalisation du Schéma OpenAPI pour un chemin + +Le dictionnaire contenu dans la variable `openapi_extra` sera fusionné avec le schéma OpenAPI généré automatiquement pour l'*opération de chemin*. + +Ainsi, vous pouvez ajouter des données supplémentaires au schéma généré automatiquement. + +Par exemple, vous pouvez décider de lire et de valider la requête avec votre propre code, sans utiliser les fonctionnalités automatiques de validation proposée par Pydantic, mais vous pouvez toujours définir la requête dans le schéma OpenAPI. + +Vous pouvez le faire avec `openapi_extra` : + +```Python hl_lines="20-37 39-40" +{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py !} +``` + +Dans cet exemple, nous n'avons déclaré aucun modèle Pydantic. En fait, le corps de la requête n'est même pas parsé en tant que JSON, il est lu directement en tant que `bytes`, et la fonction `magic_data_reader()` serait chargé de l'analyser d'une manière ou d'une autre. + +Néanmoins, nous pouvons déclarer le schéma attendu pour le corps de la requête. + +### Type de contenu OpenAPI personnalisé + +En utilisant cette même astuce, vous pouvez utiliser un modèle Pydantic pour définir le schéma JSON qui est ensuite inclus dans la section de schéma OpenAPI personnalisée pour le *chemin* concerné. + +Et vous pouvez le faire même si le type de données dans la requête n'est pas au format JSON. + +Dans cet exemple, nous n'utilisons pas les fonctionnalités de FastAPI pour extraire le schéma JSON des modèles Pydantic ni la validation automatique pour JSON. En fait, nous déclarons le type de contenu de la requête en tant que YAML, et non JSON : + +```Python hl_lines="17-22 24" +{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} +``` + +Néanmoins, bien que nous n'utilisions pas la fonctionnalité par défaut, nous utilisons toujours un modèle Pydantic pour générer manuellement le schéma JSON pour les données que nous souhaitons recevoir en YAML. + +Ensuite, nous utilisons directement la requête et extrayons son contenu en tant qu'octets. Cela signifie que FastAPI n'essaiera même pas d'analyser le payload de la requête en tant que JSON. + +Et nous analysons directement ce contenu YAML, puis nous utilisons à nouveau le même modèle Pydantic pour valider le contenu YAML : + +```Python hl_lines="26-33" +{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} +``` + +!!! Astuce + Ici, nous réutilisons le même modèle Pydantic. + + Mais nous aurions pu tout aussi bien pu le valider d'une autre manière. diff --git a/docs/fr/docs/advanced/response-directly.md b/docs/fr/docs/advanced/response-directly.md new file mode 100644 index 0000000000..1c923fb82c --- /dev/null +++ b/docs/fr/docs/advanced/response-directly.md @@ -0,0 +1,63 @@ +# Renvoyer directement une réponse + +Lorsque vous créez une *opération de chemins* **FastAPI**, vous pouvez normalement retourner n'importe quelle donnée : un `dict`, une `list`, un modèle Pydantic, un modèle de base de données, etc. + +Par défaut, **FastAPI** convertirait automatiquement cette valeur de retour en JSON en utilisant le `jsonable_encoder` expliqué dans [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. + +Ensuite, en arrière-plan, il mettra ces données JSON-compatible (par exemple un `dict`) à l'intérieur d'un `JSONResponse` qui sera utilisé pour envoyer la réponse au client. + +Mais vous pouvez retourner une `JSONResponse` directement à partir de vos *opérations de chemin*. + +Cela peut être utile, par exemple, pour retourner des en-têtes personnalisés ou des cookies. + +## Renvoyer une `Response` + +En fait, vous pouvez retourner n'importe quelle `Response` ou n'importe quelle sous-classe de celle-ci. + +!!! Note + `JSONResponse` est elle-même une sous-classe de `Response`. + +Et quand vous retournez une `Response`, **FastAPI** la transmet directement. + +Elle ne fera aucune conversion de données avec les modèles Pydantic, elle ne convertira pas le contenu en un type quelconque. + +Cela vous donne beaucoup de flexibilité. Vous pouvez retourner n'importe quel type de données, surcharger n'importe quelle déclaration ou validation de données. + +## Utiliser le `jsonable_encoder` dans une `Response` + +Parce que **FastAPI** n'apporte aucune modification à une `Response` que vous retournez, vous devez vous assurer que son contenu est prêt à être utilisé (sérialisable). + +Par exemple, vous ne pouvez pas mettre un modèle Pydantic dans une `JSONResponse` sans d'abord le convertir en un `dict` avec tous les types de données (comme `datetime`, `UUID`, etc.) convertis en types compatibles avec JSON. + +Pour ces cas, vous pouvez spécifier un appel à `jsonable_encoder` pour convertir vos données avant de les passer à une réponse : + +```Python hl_lines="6-7 21-22" +{!../../../docs_src/response_directly/tutorial001.py!} +``` + +!!! note "Détails techniques" + Vous pouvez aussi utiliser `from starlette.responses import JSONResponse`. + + **FastAPI** fournit le même objet `starlette.responses` que `fastapi.responses` juste par commodité pour le développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. + +## Renvoyer une `Response` personnalisée + +L'exemple ci-dessus montre toutes les parties dont vous avez besoin, mais il n'est pas encore très utile, car vous auriez pu retourner l'`item` directement, et **FastAPI** l'aurait mis dans une `JSONResponse` pour vous, en le convertissant en `dict`, etc. Tout cela par défaut. + +Maintenant, voyons comment vous pourriez utiliser cela pour retourner une réponse personnalisée. + +Disons que vous voulez retourner une réponse XML. + +Vous pouvez mettre votre contenu XML dans une chaîne de caractères, la placer dans une `Response`, et la retourner : + +```Python hl_lines="1 18" +{!../../../docs_src/response_directly/tutorial002.py!} +``` + +## Notes + +Lorsque vous renvoyez une `Response` directement, ses données ne sont pas validées, converties (sérialisées), ni documentées automatiquement. + +Mais vous pouvez toujours les documenter comme décrit dans [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}. + +Vous pouvez voir dans les sections suivantes comment utiliser/déclarer ces `Response`s personnalisées tout en conservant la conversion automatique des données, la documentation, etc. diff --git a/docs/fr/docs/async.md b/docs/fr/docs/async.md index 71c28b7039..db88c4663c 100644 --- a/docs/fr/docs/async.md +++ b/docs/fr/docs/async.md @@ -205,10 +205,6 @@ Cette "attente" 🕙 se mesure en microsecondes, mais tout de même, en cumulé C'est pourquoi il est logique d'utiliser du code asynchrone ⏸🔀⏯ pour des APIs web. -La plupart des frameworks Python existants (y compris Flask et Django) ont été créés avant que les nouvelles fonctionnalités asynchrones de Python n'existent. Donc, les façons dont ils peuvent être déployés supportent l'exécution parallèle et une ancienne forme d'exécution asynchrone qui n'est pas aussi puissante que les nouvelles fonctionnalités de Python. - -Et cela, bien que les spécifications principales du web asynchrone en Python (ou ASGI) ont été développées chez Django, pour ajouter le support des WebSockets. - Ce type d'asynchronicité est ce qui a rendu NodeJS populaire (bien que NodeJS ne soit pas parallèle) et c'est la force du Go en tant que langage de programmation. Et c'est le même niveau de performance que celui obtenu avec **FastAPI**. diff --git a/docs/fr/docs/deployment/deta.md b/docs/fr/docs/deployment/deta.md new file mode 100644 index 0000000000..cceb7b058c --- /dev/null +++ b/docs/fr/docs/deployment/deta.md @@ -0,0 +1,245 @@ +# Déployer FastAPI sur Deta + +Dans cette section, vous apprendrez à déployer facilement une application **FastAPI** sur Deta en utilisant le plan tarifaire gratuit. 🎁 + +Cela vous prendra environ **10 minutes**. + +!!! info + Deta sponsorise **FastAPI**. 🎉 + +## Une application **FastAPI** de base + +* Créez un répertoire pour votre application, par exemple `./fastapideta/` et déplacez-vous dedans. + +### Le code FastAPI + +* Créer un fichier `main.py` avec : + +```Python +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int): + return {"item_id": item_id} +``` + +### Dépendances + +Maintenant, dans le même répertoire, créez un fichier `requirements.txt` avec : + +```text +fastapi +``` + +!!! tip "Astuce" + Il n'est pas nécessaire d'installer Uvicorn pour déployer sur Deta, bien qu'il soit probablement souhaitable de l'installer localement pour tester votre application. + +### Structure du répertoire + +Vous aurez maintenant un répertoire `./fastapideta/` avec deux fichiers : + +``` +. +└── main.py +└── requirements.txt +``` + +## Créer un compte gratuit sur Deta + +Créez maintenant un compte gratuit +sur Deta, vous avez juste besoin d'une adresse email et d'un mot de passe. + +Vous n'avez même pas besoin d'une carte de crédit. + +## Installer le CLI (Interface en Ligne de Commande) + +Une fois que vous avez votre compte, installez le CLI de Deta : + +=== "Linux, macOS" + +
+ + ```console + $ curl -fsSL https://get.deta.dev/cli.sh | sh + ``` + +
+ +=== "Windows PowerShell" + +
+ + ```console + $ iwr https://get.deta.dev/cli.ps1 -useb | iex + ``` + +
+ +Après l'avoir installé, ouvrez un nouveau terminal afin que la nouvelle installation soit détectée. + +Dans un nouveau terminal, confirmez qu'il a été correctement installé avec : + +
+ +```console +$ deta --help + +Deta command line interface for managing deta micros. +Complete documentation available at https://docs.deta.sh + +Usage: + deta [flags] + deta [command] + +Available Commands: + auth Change auth settings for a deta micro + +... +``` + +
+ +!!! tip "Astuce" + Si vous rencontrez des problèmes pour installer le CLI, consultez la documentation officielle de Deta (en anglais). + +## Connexion avec le CLI + +Maintenant, connectez-vous à Deta depuis le CLI avec : + +
+ +```console +$ deta login + +Please, log in from the web page. Waiting.. +Logged in successfully. +``` + +
+ +Cela ouvrira un navigateur web et permettra une authentification automatique. + +## Déployer avec Deta + +Ensuite, déployez votre application avec le CLI de Deta : + +
+ +```console +$ deta new + +Successfully created a new micro + +// Notice the "endpoint" 🔍 + +{ + "name": "fastapideta", + "runtime": "python3.7", + "endpoint": "https://qltnci.deta.dev", + "visor": "enabled", + "http_auth": "enabled" +} + +Adding dependencies... + + +---> 100% + + +Successfully installed fastapi-0.61.1 pydantic-1.7.2 starlette-0.13.6 +``` + +
+ +Vous verrez un message JSON similaire à : + +```JSON hl_lines="4" +{ + "name": "fastapideta", + "runtime": "python3.7", + "endpoint": "https://qltnci.deta.dev", + "visor": "enabled", + "http_auth": "enabled" +} +``` + +!!! tip "Astuce" + Votre déploiement aura une URL `"endpoint"` différente. + +## Vérifiez + +Maintenant, dans votre navigateur ouvrez votre URL `endpoint`. Dans l'exemple ci-dessus, c'était +`https://qltnci.deta.dev`, mais la vôtre sera différente. + +Vous verrez la réponse JSON de votre application FastAPI : + +```JSON +{ + "Hello": "World" +} +``` + +Et maintenant naviguez vers `/docs` dans votre API, dans l'exemple ci-dessus ce serait `https://qltnci.deta.dev/docs`. + +Vous verrez votre documentation comme suit : + + + +## Activer l'accès public + +Par défaut, Deta va gérer l'authentification en utilisant des cookies pour votre compte. + +Mais une fois que vous êtes prêt, vous pouvez le rendre public avec : + +
+ +```console +$ deta auth disable + +Successfully disabled http auth +``` + +
+ +Maintenant, vous pouvez partager cette URL avec n'importe qui et ils seront en mesure d'accéder à votre API. 🚀 + +## HTTPS + +Félicitations ! Vous avez déployé votre application FastAPI sur Deta ! 🎉 🍰 + +Remarquez également que Deta gère correctement HTTPS pour vous, vous n'avez donc pas à vous en occuper et pouvez être sûr que vos clients auront une connexion cryptée sécurisée. ✅ 🔒 + +## Vérifiez le Visor + +À partir de l'interface graphique de votre documentation (dans une URL telle que `https://qltnci.deta.dev/docs`) +envoyez une requête à votre *opération de chemin* `/items/{item_id}`. + +Par exemple avec l'ID `5`. + +Allez maintenant sur https://web.deta.sh. + +Vous verrez qu'il y a une section à gauche appelée "Micros" avec chacune de vos applications. + +Vous verrez un onglet avec "Details", et aussi un onglet "Visor", allez à l'onglet "Visor". + +Vous pouvez y consulter les requêtes récentes envoyées à votre application. + +Vous pouvez également les modifier et les relancer. + + + +## En savoir plus + +À un moment donné, vous voudrez probablement stocker certaines données pour votre application d'une manière qui +persiste dans le temps. Pour cela, vous pouvez utiliser Deta Base, il dispose également d'un généreux **plan gratuit**. + +Vous pouvez également en lire plus dans la documentation Deta. diff --git a/docs/fr/docs/deployment/https.md b/docs/fr/docs/deployment/https.md new file mode 100644 index 0000000000..ccf1f847a0 --- /dev/null +++ b/docs/fr/docs/deployment/https.md @@ -0,0 +1,53 @@ +# À propos de HTTPS + +Il est facile de penser que HTTPS peut simplement être "activé" ou non. + +Mais c'est beaucoup plus complexe que cela. + +!!! tip + Si vous êtes pressé ou si cela ne vous intéresse pas, passez aux sections suivantes pour obtenir des instructions étape par étape afin de tout configurer avec différentes techniques. + +Pour apprendre les bases du HTTPS, du point de vue d'un utilisateur, consultez https://howhttps.works/. + +Maintenant, du point de vue d'un développeur, voici plusieurs choses à avoir en tête en pensant au HTTPS : + +* Pour le HTTPS, le serveur a besoin de "certificats" générés par une tierce partie. + * Ces certificats sont en fait acquis auprès de la tierce partie, et non "générés". +* Les certificats ont une durée de vie. + * Ils expirent. + * Puis ils doivent être renouvelés et acquis à nouveau auprès de la tierce partie. +* Le cryptage de la connexion se fait au niveau du protocole TCP. + * C'est une couche en dessous de HTTP. + * Donc, le certificat et le traitement du cryptage sont faits avant HTTP. +* TCP ne connaît pas les "domaines", seulement les adresses IP. + * L'information sur le domaine spécifique demandé se trouve dans les données HTTP. +* Les certificats HTTPS "certifient" un certain domaine, mais le protocole et le cryptage se font au niveau TCP, avant de savoir quel domaine est traité. +* Par défaut, cela signifie que vous ne pouvez avoir qu'un seul certificat HTTPS par adresse IP. + * Quelle que soit la taille de votre serveur ou la taille de chacune des applications qu'il contient. + * Il existe cependant une solution à ce problème. +* Il existe une extension du protocole TLS (celui qui gère le cryptage au niveau TCP, avant HTTP) appelée SNI (indication du nom du serveur). + * Cette extension SNI permet à un seul serveur (avec une seule adresse IP) d'avoir plusieurs certificats HTTPS et de servir plusieurs domaines/applications HTTPS. + * Pour que cela fonctionne, un seul composant (programme) fonctionnant sur le serveur, écoutant sur l'adresse IP publique, doit avoir tous les certificats HTTPS du serveur. +* Après avoir obtenu une connexion sécurisée, le protocole de communication est toujours HTTP. + * Le contenu est crypté, même s'il est envoyé avec le protocole HTTP. + +Il est courant d'avoir un seul programme/serveur HTTP fonctionnant sur le serveur (la machine, l'hôte, etc.) et +gérant toutes les parties HTTPS : envoyer les requêtes HTTP décryptées à l'application HTTP réelle fonctionnant sur +le même serveur (dans ce cas, l'application **FastAPI**), prendre la réponse HTTP de l'application, la crypter en utilisant le certificat approprié et la renvoyer au client en utilisant HTTPS. Ce serveur est souvent appelé un Proxy de terminaison TLS. + +## Let's Encrypt + +Avant Let's Encrypt, ces certificats HTTPS étaient vendus par des tiers de confiance. + +Le processus d'acquisition d'un de ces certificats était auparavant lourd, nécessitait pas mal de paperasses et les certificats étaient assez chers. + +Mais ensuite, Let's Encrypt a été créé. + +Il s'agit d'un projet de la Fondation Linux. Il fournit des certificats HTTPS gratuitement. De manière automatisée. Ces certificats utilisent toutes les sécurités cryptographiques standard et ont une durée de vie courte (environ 3 mois), de sorte que la sécurité est en fait meilleure en raison de leur durée de vie réduite. + +Les domaines sont vérifiés de manière sécurisée et les certificats sont générés automatiquement. Cela permet également d'automatiser le renouvellement de ces certificats. + +L'idée est d'automatiser l'acquisition et le renouvellement de ces certificats, afin que vous puissiez disposer d'un HTTPS sécurisé, gratuitement et pour toujours. diff --git a/docs/fr/docs/deployment/index.md b/docs/fr/docs/deployment/index.md new file mode 100644 index 0000000000..e2014afe95 --- /dev/null +++ b/docs/fr/docs/deployment/index.md @@ -0,0 +1,28 @@ +# Déploiement + +Le déploiement d'une application **FastAPI** est relativement simple. + +## Que signifie le déploiement + +**Déployer** une application signifie effectuer les étapes nécessaires pour la rendre **disponible pour les +utilisateurs**. + +Pour une **API Web**, cela implique normalement de la placer sur une **machine distante**, avec un **programme serveur** +qui offre de bonnes performances, une bonne stabilité, _etc._, afin que vos **utilisateurs** puissent **accéder** à +l'application efficacement et sans interruption ni problème. + +Ceci contraste avec les étapes de **développement**, où vous êtes constamment en train de modifier le code, de le casser +et de le réparer, d'arrêter et de redémarrer le serveur de développement, _etc._ + +## Stratégies de déploiement + +Il existe plusieurs façons de procéder, en fonction de votre cas d'utilisation spécifique et des outils que vous +utilisez. + +Vous pouvez **déployer un serveur** vous-même en utilisant une combinaison d'outils, vous pouvez utiliser un **service +cloud** qui fait une partie du travail pour vous, ou encore d'autres options possibles. + +Je vais vous montrer certains des principaux concepts que vous devriez probablement avoir à l'esprit lors du déploiement +d'une application **FastAPI** (bien que la plupart de ces concepts s'appliquent à tout autre type d'application web). + +Vous verrez plus de détails à avoir en tête et certaines des techniques pour le faire dans les sections suivantes. ✨ diff --git a/docs/fr/docs/deployment/manually.md b/docs/fr/docs/deployment/manually.md new file mode 100644 index 0000000000..c53e2db677 --- /dev/null +++ b/docs/fr/docs/deployment/manually.md @@ -0,0 +1,153 @@ +# Exécuter un serveur manuellement - Uvicorn + +La principale chose dont vous avez besoin pour exécuter une application **FastAPI** sur une machine serveur distante est un programme serveur ASGI tel que **Uvicorn**. + +Il existe 3 principales alternatives : + +* Uvicorn : un serveur ASGI haute performance. +* Hypercorn : un serveur + ASGI compatible avec HTTP/2 et Trio entre autres fonctionnalités. +* Daphne : le serveur ASGI + conçu pour Django Channels. + +## Machine serveur et programme serveur + +Il y a un petit détail sur les noms à garder à l'esprit. 💡 + +Le mot "**serveur**" est couramment utilisé pour désigner à la fois l'ordinateur distant/cloud (la machine physique ou virtuelle) et également le programme qui s'exécute sur cette machine (par exemple, Uvicorn). + +Gardez cela à l'esprit lorsque vous lisez "serveur" en général, cela pourrait faire référence à l'une de ces deux choses. + +Lorsqu'on se réfère à la machine distante, il est courant de l'appeler **serveur**, mais aussi **machine**, **VM** (machine virtuelle), **nœud**. Tout cela fait référence à un type de machine distante, exécutant Linux, en règle générale, sur laquelle vous exécutez des programmes. + + +## Installer le programme serveur + +Vous pouvez installer un serveur compatible ASGI avec : + +=== "Uvicorn" + + * Uvicorn, un serveur ASGI rapide comme l'éclair, basé sur uvloop et httptools. + +
+ + ```console + $ pip install "uvicorn[standard]" + + ---> 100% + ``` + +
+ + !!! tip "Astuce" + En ajoutant `standard`, Uvicorn va installer et utiliser quelques dépendances supplémentaires recommandées. + + Cela inclut `uvloop`, le remplaçant performant de `asyncio`, qui fournit le gros gain de performance en matière de concurrence. + +=== "Hypercorn" + + * Hypercorn, un serveur ASGI également compatible avec HTTP/2. + +
+ + ```console + $ pip install hypercorn + + ---> 100% + ``` + +
+ + ...ou tout autre serveur ASGI. + +## Exécutez le programme serveur + +Vous pouvez ensuite exécuter votre application de la même manière que vous l'avez fait dans les tutoriels, mais sans l'option `--reload`, par exemple : + +=== "Uvicorn" + +
+ + ```console + $ uvicorn main:app --host 0.0.0.0 --port 80 + + INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) + ``` + +
+ +=== "Hypercorn" + +
+ + ```console + $ hypercorn main:app --bind 0.0.0.0:80 + + Running on 0.0.0.0:8080 over http (CTRL + C to quit) + ``` + +
+ +!!! warning + N'oubliez pas de supprimer l'option `--reload` si vous l'utilisiez. + + L'option `--reload` consomme beaucoup plus de ressources, est plus instable, etc. + + Cela aide beaucoup pendant le **développement**, mais vous **ne devriez pas** l'utiliser en **production**. + +## Hypercorn avec Trio + +Starlette et **FastAPI** sont basés sur +AnyIO, qui les rend +compatibles avec asyncio, de la bibliothèque standard Python et +Trio. + +Néanmoins, Uvicorn n'est actuellement compatible qu'avec asyncio, et il utilise normalement `uvloop`, le remplaçant hautes performances de `asyncio`. + +Mais si vous souhaitez utiliser directement **Trio**, vous pouvez utiliser **Hypercorn** car il le prend en charge. ✨ + +### Installer Hypercorn avec Trio + +Vous devez d'abord installer Hypercorn avec le support Trio : + +
+ +```console +$ pip install "hypercorn[trio]" +---> 100% +``` + +
+ +### Exécuter avec Trio + +Ensuite, vous pouvez passer l'option de ligne de commande `--worker-class` avec la valeur `trio` : + +
+ +```console +$ hypercorn main:app --worker-class trio +``` + +
+ +Et cela démarrera Hypercorn avec votre application en utilisant Trio comme backend. + +Vous pouvez désormais utiliser Trio en interne dans votre application. Ou mieux encore, vous pouvez utiliser AnyIO pour que votre code reste compatible avec Trio et asyncio. 🎉 + +## Concepts de déploiement + +Ces exemples lancent le programme serveur (e.g. Uvicorn), démarrant **un seul processus**, sur toutes les IPs (`0.0. +0.0`) sur un port prédéfini (par example, `80`). + +C'est l'idée de base. Mais vous vous préoccuperez probablement de certains concepts supplémentaires, tels que ... : + +* la sécurité - HTTPS +* l'exécution au démarrage +* les redémarrages +* la réplication (le nombre de processus en cours d'exécution) +* la mémoire +* les étapes précédant le démarrage + +Je vous en dirai plus sur chacun de ces concepts, sur la façon de les aborder, et donnerai quelques exemples concrets avec des stratégies pour les traiter dans les prochains chapitres. 🚀 diff --git a/docs/fr/docs/deployment/versions.md b/docs/fr/docs/deployment/versions.md new file mode 100644 index 0000000000..136165e9d1 --- /dev/null +++ b/docs/fr/docs/deployment/versions.md @@ -0,0 +1,95 @@ +# À propos des versions de FastAPI + +**FastAPI** est déjà utilisé en production dans de nombreuses applications et systèmes. Et la couverture de test est maintenue à 100 %. Mais son développement est toujours aussi rapide. + +De nouvelles fonctionnalités sont ajoutées fréquemment, des bogues sont corrigés régulièrement et le code est +amélioré continuellement. + +C'est pourquoi les versions actuelles sont toujours `0.x.x`, cela reflète que chaque version peut potentiellement +recevoir des changements non rétrocompatibles. Cela suit les conventions de versionnage sémantique. + +Vous pouvez créer des applications de production avec **FastAPI** dès maintenant (et vous le faites probablement depuis un certain temps), vous devez juste vous assurer que vous utilisez une version qui fonctionne correctement avec le reste de votre code. + +## Épinglez votre version de `fastapi` + +Tout d'abord il faut "épingler" la version de **FastAPI** que vous utilisez à la dernière version dont vous savez +qu'elle fonctionne correctement pour votre application. + +Par exemple, disons que vous utilisez la version `0.45.0` dans votre application. + +Si vous utilisez un fichier `requirements.txt`, vous pouvez spécifier la version avec : + +```txt +fastapi==0.45.0 +``` + +ce qui signifierait que vous utiliseriez exactement la version `0.45.0`. + +Ou vous pourriez aussi l'épingler avec : + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +cela signifierait que vous utiliseriez les versions `0.45.0` ou supérieures, mais inférieures à `0.46.0`, par exemple, une version `0.45.2` serait toujours acceptée. + +Si vous utilisez un autre outil pour gérer vos installations, comme Poetry, Pipenv, ou autres, ils ont tous un moyen que vous pouvez utiliser pour définir des versions spécifiques pour vos paquets. + +## Versions disponibles + +Vous pouvez consulter les versions disponibles (par exemple, pour vérifier quelle est la dernière version en date) dans les [Notes de version](../release-notes.md){.internal-link target=_blank}. + +## À propos des versions + +Suivant les conventions de versionnage sémantique, toute version inférieure à `1.0.0` peut potentiellement ajouter +des changements non rétrocompatibles. + +FastAPI suit également la convention que tout changement de version "PATCH" est pour des corrections de bogues et +des changements rétrocompatibles. + +!!! tip "Astuce" + Le "PATCH" est le dernier chiffre, par exemple, dans `0.2.3`, la version PATCH est `3`. + +Donc, vous devriez être capable d'épingler une version comme suit : + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +Les changements non rétrocompatibles et les nouvelles fonctionnalités sont ajoutés dans les versions "MINOR". + +!!! tip "Astuce" + Le "MINOR" est le numéro au milieu, par exemple, dans `0.2.3`, la version MINOR est `2`. + +## Mise à jour des versions FastAPI + +Vous devriez tester votre application. + +Avec **FastAPI** c'est très facile (merci à Starlette), consultez la documentation : [Testing](../tutorial/testing.md){.internal-link target=_blank} + +Après avoir effectué des tests, vous pouvez mettre à jour la version **FastAPI** vers une version plus récente, et vous assurer que tout votre code fonctionne correctement en exécutant vos tests. + +Si tout fonctionne, ou après avoir fait les changements nécessaires, et que tous vos tests passent, vous pouvez +épingler votre version de `fastapi` à cette nouvelle version récente. + +## À propos de Starlette + +Vous ne devriez pas épingler la version de `starlette`. + +Différentes versions de **FastAPI** utiliseront une version spécifique plus récente de Starlette. + +Ainsi, vous pouvez simplement laisser **FastAPI** utiliser la bonne version de Starlette. + +## À propos de Pydantic + +Pydantic inclut des tests pour **FastAPI** avec ses propres tests, ainsi les nouvelles versions de Pydantic (au-dessus +de `1.0.0`) sont toujours compatibles avec **FastAPI**. + +Vous pouvez épingler Pydantic à toute version supérieure à `1.0.0` qui fonctionne pour vous et inférieure à `2.0.0`. + +Par exemple : + +```txt +pydantic>=1.2.0,<2.0.0 +``` diff --git a/docs/fr/docs/help-fastapi.md b/docs/fr/docs/help-fastapi.md new file mode 100644 index 0000000000..0995721e1e --- /dev/null +++ b/docs/fr/docs/help-fastapi.md @@ -0,0 +1,122 @@ +# Help FastAPI - Obtenir de l'aide + +Aimez-vous **FastAPI** ? + +Vous souhaitez aider FastAPI, les autres utilisateurs et l'auteur ? + +Ou souhaitez-vous obtenir de l'aide avec le **FastAPI** ? + +Il existe des moyens très simples d'aider (plusieurs ne nécessitent qu'un ou deux clics). + +Il existe également plusieurs façons d'obtenir de l'aide. + +## Star **FastAPI** sur GitHub + +Vous pouvez "star" FastAPI dans GitHub (en cliquant sur le bouton étoile en haut à droite) : https://github.com/tiangolo/fastapi. ⭐️ + +En ajoutant une étoile, les autres utilisateurs pourront la trouver plus facilement et constater qu'elle a déjà été utile à d'autres. + +## Watch le dépôt GitHub pour les releases + +Vous pouvez "watch" FastAPI dans GitHub (en cliquant sur le bouton "watch" en haut à droite) : https://github.com/tiangolo/fastapi. 👀 + +Vous pouvez y sélectionner "Releases only". + +Ainsi, vous recevrez des notifications (dans votre courrier électronique) chaque fois qu'il y aura une nouvelle version de **FastAPI** avec des corrections de bugs et de nouvelles fonctionnalités. + +## Se rapprocher de l'auteur + +Vous pouvez vous rapprocher de moi (Sebastián Ramírez / `tiangolo`), l'auteur. + +Vous pouvez : + +* Me suivre sur **GitHub**. + * Voir d'autres projets Open Source que j'ai créés et qui pourraient vous aider. + * Suivez-moi pour voir quand je crée un nouveau projet Open Source. +* Me suivre sur **Twitter**. + * Dites-moi comment vous utilisez FastAPI (j'adore entendre ça). + * Entendre quand je fais des annonces ou que je lance de nouveaux outils. +* Vous connectez à moi sur **Linkedin**. + * Etre notifié quand je fais des annonces ou que je lance de nouveaux outils (bien que j'utilise plus souvent Twitte 🤷‍♂). +* Lire ce que j’écris (ou me suivre) sur **Dev.to** ou **Medium**. + * Lire d'autres idées, articles, et sur les outils que j'ai créés. + * Suivez-moi pour lire quand je publie quelque chose de nouveau. + +## Tweeter sur **FastAPI** + +Tweetez à propos de **FastAPI** et faites-moi savoir, ainsi qu'aux autres, pourquoi vous aimez ça. 🎉 + +J'aime entendre parler de l'utilisation du **FastAPI**, de ce que vous avez aimé dedans, dans quel projet/entreprise l'utilisez-vous, etc. + +## Voter pour FastAPI + +* Votez pour **FastAPI** sur Slant. +* Votez pour **FastAPI** sur AlternativeTo. +* Votez pour **FastAPI** sur awesome-rest. + +## Aider les autres à résoudre les problèmes dans GitHub + +Vous pouvez voir les problèmes existants et essayer d'aider les autres, la plupart du temps il s'agit de questions dont vous connaissez peut-être déjà la réponse. 🤓 + +## Watch le dépôt GitHub + +Vous pouvez "watch" FastAPI dans GitHub (en cliquant sur le bouton "watch" en haut à droite) : https://github.com/tiangolo/fastapi. 👀 + +Si vous sélectionnez "Watching" au lieu de "Releases only", vous recevrez des notifications lorsque quelqu'un crée une nouvelle Issue. + +Vous pouvez alors essayer de les aider à résoudre ces problèmes. + +## Créer une Issue + +Vous pouvez créer une Issue dans le dépôt GitHub, par exemple pour : + +* Poser une question ou s'informer sur un problème. +* Suggérer une nouvelle fonctionnalité. + +**Note** : si vous créez un problème, alors je vais vous demander d'aider aussi les autres. 😉 + +## Créer une Pull Request + +Vous pouvez créer une Pull Request, par exemple : + +* Pour corriger une faute de frappe que vous avez trouvée sur la documentation. +* Proposer de nouvelles sections de documentation. +* Pour corriger une Issue/Bug existant. +* Pour ajouter une nouvelle fonctionnalité. + +## Rejoindre le chat + + + Rejoindre le chat à https://gitter.im/tiangolo/fastapi + + +Rejoignez le chat sur Gitter: https://gitter.im/tiangolo/fastapi. + +Vous pouvez y avoir des conversations rapides avec d'autres personnes, aider les autres, partager des idées, etc. + +Mais gardez à l'esprit que, comme il permet une "conversation plus libre", il est facile de poser des questions trop générales et plus difficiles à répondre, de sorte que vous risquez de ne pas recevoir de réponses. + +Dans les Issues de GitHub, le modèle vous guidera pour écrire la bonne question afin que vous puissiez plus facilement obtenir une bonne réponse, ou même résoudre le problème vous-même avant même de le poser. Et dans GitHub, je peux m'assurer que je réponds toujours à tout, même si cela prend du temps. Je ne peux pas faire cela personnellement avec le chat Gitter. 😅 + +Les conversations dans Gitter ne sont pas non plus aussi facilement consultables que dans GitHub, de sorte que les questions et les réponses peuvent se perdre dans la conversation. + +De l'autre côté, il y a plus de 1000 personnes dans le chat, il y a donc de fortes chances que vous y trouviez quelqu'un à qui parler, presque tout le temps. 😄 + +## Parrainer l'auteur + +Vous pouvez également soutenir financièrement l'auteur (moi) via GitHub sponsors. + +Là, vous pourriez m'offrir un café ☕️ pour me remercier 😄. + +## Sponsoriser les outils qui font fonctionner FastAPI + +Comme vous l'avez vu dans la documentation, FastAPI se tient sur les épaules des géants, Starlette et Pydantic. + +Vous pouvez également parrainer : + +* Samuel Colvin (Pydantic) +* Encode (Starlette, Uvicorn) + +--- + +Merci ! 🚀 diff --git a/docs/fr/docs/history-design-future.md b/docs/fr/docs/history-design-future.md new file mode 100644 index 0000000000..b77664be6c --- /dev/null +++ b/docs/fr/docs/history-design-future.md @@ -0,0 +1,79 @@ +# Histoire, conception et avenir + +Il y a quelque temps, un utilisateur de **FastAPI** a demandé : + +> Quelle est l'histoire de ce projet ? Il semble être sorti de nulle part et est devenu génial en quelques semaines [...]. + +Voici un petit bout de cette histoire. + +## Alternatives + +Je crée des API avec des exigences complexes depuis plusieurs années (Machine Learning, systèmes distribués, jobs asynchrones, bases de données NoSQL, etc), en dirigeant plusieurs équipes de développeurs. + +Dans ce cadre, j'ai dû étudier, tester et utiliser de nombreuses alternatives. + +L'histoire de **FastAPI** est en grande partie l'histoire de ses prédécesseurs. + +Comme dit dans la section [Alternatives](alternatives.md){.internal-link target=\_blank} : + +
+ +**FastAPI** n'existerait pas sans le travail antérieur d'autres personnes. + +Il y a eu de nombreux outils créés auparavant qui ont contribué à inspirer sa création. + +J'ai évité la création d'un nouveau framework pendant plusieurs années. J'ai d'abord essayé de résoudre toutes les fonctionnalités couvertes par **FastAPI** en utilisant de nombreux frameworks, plug-ins et outils différents. + +Mais à un moment donné, il n'y avait pas d'autre option que de créer quelque chose qui offre toutes ces fonctionnalités, en prenant les meilleures idées des outils précédents, et en les combinant de la meilleure façon possible, en utilisant des fonctionnalités du langage qui n'étaient même pas disponibles auparavant (annotations de type pour Python 3.6+). + +
+ +## Recherche + +En utilisant toutes les alternatives précédentes, j'ai eu la chance d'apprendre de toutes, de prendre des idées, et de les combiner de la meilleure façon que j'ai pu trouver pour moi-même et les équipes de développeurs avec lesquelles j'ai travaillé. + +Par exemple, il était clair que l'idéal était de se baser sur les annotations de type Python standard. + +De plus, la meilleure approche était d'utiliser des normes déjà existantes. + +Ainsi, avant même de commencer à coder **FastAPI**, j'ai passé plusieurs mois à étudier les spécifications d'OpenAPI, JSON Schema, OAuth2, etc. Comprendre leurs relations, leurs similarités et leurs différences. + +## Conception + +Ensuite, j'ai passé du temps à concevoir l'"API" de développeur que je voulais avoir en tant qu'utilisateur (en tant que développeur utilisant FastAPI). + +J'ai testé plusieurs idées dans les éditeurs Python les plus populaires : PyCharm, VS Code, les éditeurs basés sur Jedi. + +D'après la dernière Enquête Développeurs Python, cela couvre environ 80% des utilisateurs. + +Cela signifie que **FastAPI** a été spécifiquement testé avec les éditeurs utilisés par 80% des développeurs Python. Et comme la plupart des autres éditeurs ont tendance à fonctionner de façon similaire, tous ses avantages devraient fonctionner pour pratiquement tous les éditeurs. + +Ainsi, j'ai pu trouver les meilleurs moyens de réduire autant que possible la duplication du code, d'avoir la complétion partout, les contrôles de type et d'erreur, etc. + +Le tout de manière à offrir la meilleure expérience de développement à tous les développeurs. + +## Exigences + +Après avoir testé plusieurs alternatives, j'ai décidé que j'allais utiliser **Pydantic** pour ses avantages. + +J'y ai ensuite contribué, pour le rendre entièrement compatible avec JSON Schema, pour supporter différentes manières de définir les déclarations de contraintes, et pour améliorer le support des éditeurs (vérifications de type, autocomplétion) sur la base des tests effectués dans plusieurs éditeurs. + +Pendant le développement, j'ai également contribué à **Starlette**, l'autre exigence clé. + +## Développement + +Au moment où j'ai commencé à créer **FastAPI** lui-même, la plupart des pièces étaient déjà en place, la conception était définie, les exigences et les outils étaient prêts, et la connaissance des normes et des spécifications était claire et fraîche. + +## Futur + +À ce stade, il est déjà clair que **FastAPI** et ses idées sont utiles pour de nombreuses personnes. + +Elle a été préférée aux solutions précédentes parce qu'elle convient mieux à de nombreux cas d'utilisation. + +De nombreux développeurs et équipes dépendent déjà de **FastAPI** pour leurs projets (y compris moi et mon équipe). + +Mais il y a encore de nombreuses améliorations et fonctionnalités à venir. + +**FastAPI** a un grand avenir devant lui. + +Et [votre aide](help-fastapi.md){.internal-link target=\_blank} est grandement appréciée. diff --git a/docs/fr/docs/index.md b/docs/fr/docs/index.md index 0b537054e9..7c7547be1c 100644 --- a/docs/fr/docs/index.md +++ b/docs/fr/docs/index.md @@ -1,48 +1,46 @@ - -{!../../../docs/missing-translation.md!} - -

FastAPI

- FastAPI framework, high performance, easy to learn, fast to code, ready for production + Framework FastAPI, haute performance, facile à apprendre, rapide à coder, prêt pour la production

- - Test + + Test - - Coverage + + Coverage Package version + + Supported Python versions +

--- -**Documentation**: https://fastapi.tiangolo.com +**Documentation** : https://fastapi.tiangolo.com -**Source Code**: https://github.com/tiangolo/fastapi +**Code Source** : https://github.com/tiangolo/fastapi --- -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. +FastAPI est un framework web moderne et rapide (haute performance) pour la création d'API avec Python 3.7+, basé sur les annotations de type standard de Python. -The key features are: +Les principales fonctionnalités sont : -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). +* **Rapidité** : De très hautes performances, au niveau de **NodeJS** et **Go** (grâce à Starlette et Pydantic). [L'un des frameworks Python les plus rapides](#performance). +* **Rapide à coder** : Augmente la vitesse de développement des fonctionnalités d'environ 200 % à 300 %. * +* **Moins de bugs** : Réduit d'environ 40 % les erreurs induites par le développeur. * +* **Intuitif** : Excellente compatibilité avec les IDE. Complétion complète. Moins de temps passé à déboguer. +* **Facile** : Conçu pour être facile à utiliser et à apprendre. Moins de temps passé à lire la documentation. +* **Concis** : Diminue la duplication de code. De nombreuses fonctionnalités liées à la déclaration de chaque paramètre. Moins de bugs. +* **Robuste** : Obtenez un code prêt pour la production. Avec une documentation interactive automatique. +* **Basé sur des normes** : Basé sur (et entièrement compatible avec) les standards ouverts pour les APIs : OpenAPI (précédemment connu sous le nom de Swagger) et JSON Schema. -* **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. +* estimation basée sur des tests d'une équipe de développement interne, construisant des applications de production. ## Sponsors @@ -63,60 +61,66 @@ The key features are: ## Opinions -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" +"_[...] J'utilise beaucoup **FastAPI** ces derniers temps. [...] Je prévois de l'utiliser dans mon équipe pour tous les **services de ML chez Microsoft**. Certains d'entre eux seront intégrés dans le coeur de **Windows** et dans certains produits **Office**._"
Kabir Khan - Microsoft (ref)
--- -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" +"_Nous avons adopté la bibliothèque **FastAPI** pour créer un serveur **REST** qui peut être interrogé pour obtenir des **prédictions**. [pour Ludwig]_" -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
+
Piero Molino, Yaroslav Dudin et Sai Sumanth Miryala - Uber (ref)
--- -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" +"_**Netflix** a le plaisir d'annoncer la sortie en open-source de notre framework d'orchestration de **gestion de crise** : **Dispatch** ! [construit avec **FastAPI**]_"
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
--- -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" +"_Je suis très enthousiaste à propos de **FastAPI**. C'est un bonheur !_" -
Brian Okken - Python Bytes podcast host (ref)
+
Brian Okken - Auteur du podcast Python Bytes (ref)
--- -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" +"_Honnêtement, ce que vous avez construit a l'air super solide et élégant. A bien des égards, c'est comme ça que je voulais que **Hug** soit - c'est vraiment inspirant de voir quelqu'un construire ça._" -
Timothy Crosley - Hug creator (ref)
+
Timothy Crosley - Créateur de Hug (ref)
--- -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" +"_Si vous cherchez à apprendre un **framework moderne** pour créer des APIs REST, regardez **FastAPI** [...] C'est rapide, facile à utiliser et à apprendre [...]_" -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" +"_Nous sommes passés à **FastAPI** pour nos **APIs** [...] Je pense que vous l'aimerez [...]_" -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
+
Ines Montani - Matthew Honnibal - Fondateurs de Explosion AI - Créateurs de spaCy (ref) - (ref)
--- -## **Typer**, the FastAPI of CLIs +"_Si quelqu'un cherche à construire une API Python de production, je recommande vivement **FastAPI**. Il est **bien conçu**, **simple à utiliser** et **très évolutif**. Il est devenu un **composant clé** dans notre stratégie de développement API first et il est à l'origine de nombreux automatismes et services tels que notre ingénieur virtuel TAC._" + +
Deon Pillsbury - Cisco (ref)
+ +--- + +## **Typer**, le FastAPI des CLI -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. +Si vous souhaitez construire une application CLI utilisable dans un terminal au lieu d'une API web, regardez **Typer**. -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 +**Typer** est le petit frère de FastAPI. Et il est destiné à être le **FastAPI des CLI**. ⌨️ 🚀 -## Requirements +## Prérequis -Python 3.6+ +Python 3.7+ -FastAPI stands on the shoulders of giants: +FastAPI repose sur les épaules de géants : -* Starlette for the web parts. -* Pydantic for the data parts. +* Starlette pour les parties web. +* Pydantic pour les parties données. ## Installation @@ -130,26 +134,26 @@ $ pip install fastapi
-You will also need an ASGI server, for production such as Uvicorn or Hypercorn. +Vous aurez également besoin d'un serveur ASGI pour la production tel que Uvicorn ou Hypercorn.
```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ```
-## Example +## Exemple -### Create it +### Créez -* Create a file `main.py` with: +* Créez un fichier `main.py` avec : ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -162,17 +166,17 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ```
-Or use async def... +Ou utilisez async def ... -If your code uses `async` / `await`, use `async def`: +Si votre code utilise `async` / `await`, utilisez `async def` : -```Python hl_lines="9 14" -from typing import Optional +```Python hl_lines="9 14" +from typing import Union from fastapi import FastAPI @@ -185,19 +189,19 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -**Note**: +**Note** -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. +Si vous n'êtes pas familier avec cette notion, consultez la section _"Vous êtes pressés ?"_ à propos de `async` et `await` dans la documentation.
-### Run it +### Lancez -Run the server with: +Lancez le serveur avec :
@@ -214,57 +218,57 @@ INFO: Application startup complete.
-About the command uvicorn main:app --reload... +À propos de la commande uvicorn main:app --reload ... -The command `uvicorn main:app` refers to: +La commande `uvicorn main:app` fait référence à : -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. +* `main` : le fichier `main.py` (le "module" Python). +* `app` : l'objet créé à l'intérieur de `main.py` avec la ligne `app = FastAPI()`. +* `--reload` : fait redémarrer le serveur après des changements de code. À n'utiliser que pour le développement.
-### Check it +### Vérifiez -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. +Ouvrez votre navigateur à l'adresse http://127.0.0.1:8000/items/5?q=somequery. -You will see the JSON response as: +Vous obtenez alors cette réponse JSON : ```JSON {"item_id": 5, "q": "somequery"} ``` -You already created an API that: +Vous venez de créer une API qui : -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. +* Reçoit les requêtes HTTP pour les _chemins_ `/` et `/items/{item_id}`. +* Les deux _chemins_ acceptent des opérations `GET` (également connu sous le nom de _méthodes_ HTTP). +* Le _chemin_ `/items/{item_id}` a un _paramètre_ `item_id` qui doit être un `int`. +* Le _chemin_ `/items/{item_id}` a un _paramètre de requête_ optionnel `q` de type `str`. -### Interactive API docs +### Documentation API interactive -Now go to http://127.0.0.1:8000/docs. +Maintenant, rendez-vous sur http://127.0.0.1:8000/docs. -You will see the automatic interactive API documentation (provided by Swagger UI): +Vous verrez la documentation interactive automatique de l'API (fournie par Swagger UI) : ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Alternative API docs +### Documentation API alternative -And now, go to http://127.0.0.1:8000/redoc. +Et maintenant, rendez-vous sur http://127.0.0.1:8000/redoc. -You will see the alternative automatic documentation (provided by ReDoc): +Vous verrez la documentation interactive automatique de l'API (fournie par ReDoc) : ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Example upgrade +## Exemple plus poussé -Now modify the file `main.py` to receive a body from a `PUT` request. +Maintenant, modifiez le fichier `main.py` pour recevoir le corps d'une requête `PUT`. -Declare the body using standard Python types, thanks to Pydantic. +Déclarez ce corps en utilisant les types Python standards, grâce à Pydantic. -```Python hl_lines="4 9 10 11 12 25 26 27" -from typing import Optional +```Python hl_lines="4 9-12 25-27" +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -275,7 +279,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -284,7 +288,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -293,174 +297,172 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). +Le serveur se recharge normalement automatiquement (car vous avez pensé à `--reload` dans la commande `uvicorn` ci-dessus). -### Interactive API docs upgrade +### Plus loin avec la documentation API interactive -Now go to http://127.0.0.1:8000/docs. +Maintenant, rendez-vous sur http://127.0.0.1:8000/docs. -* The interactive API documentation will be automatically updated, including the new body: +* La documentation interactive de l'API sera automatiquement mise à jour, y compris le nouveau corps de la requête : ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: +* Cliquez sur le bouton "Try it out", il vous permet de renseigner les paramètres et d'interagir directement avec l'API : ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: +* Cliquez ensuite sur le bouton "Execute", l'interface utilisateur communiquera avec votre API, enverra les paramètres, obtiendra les résultats et les affichera à l'écran : ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### Alternative API docs upgrade +### Plus loin avec la documentation API alternative -And now, go to http://127.0.0.1:8000/redoc. +Et maintenant, rendez-vous sur http://127.0.0.1:8000/redoc. -* The alternative documentation will also reflect the new query parameter and body: +* La documentation alternative reflétera également le nouveau paramètre de requête et le nouveau corps : ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Recap +### En résumé -In summary, you declare **once** the types of parameters, body, etc. as function parameters. +En résumé, vous déclarez **une fois** les types de paramètres, le corps de la requête, etc. en tant que paramètres de fonction. -You do that with standard modern Python types. +Vous faites cela avec les types Python standard modernes. -You don't have to learn a new syntax, the methods or classes of a specific library, etc. +Vous n'avez pas à apprendre une nouvelle syntaxe, les méthodes ou les classes d'une bibliothèque spécifique, etc. -Just standard **Python 3.6+**. +Juste du **Python 3.7+** standard. -For example, for an `int`: +Par exemple, pour un `int`: ```Python item_id: int ``` -or for a more complex `Item` model: +ou pour un modèle `Item` plus complexe : ```Python item: Item ``` -...and with that single declaration you get: +... et avec cette déclaration unique, vous obtenez : -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: +* Une assistance dans votre IDE, notamment : + * la complétion. + * la vérification des types. +* La validation des données : + * des erreurs automatiques et claires lorsque les données ne sont pas valides. + * une validation même pour les objets JSON profondément imbriqués. +* Une conversion des données d'entrée : venant du réseau et allant vers les données et types de Python, permettant de lire : + * le JSON. + * les paramètres du chemin. + * les paramètres de la requête. + * les cookies. + * les en-têtes. + * les formulaires. + * les fichiers. +* La conversion des données de sortie : conversion des données et types Python en données réseau (au format JSON), permettant de convertir : + * les types Python (`str`, `int`, `float`, `bool`, `list`, etc). + * les objets `datetime`. + * les objets `UUID`. + * les modèles de base de données. + * ... et beaucoup plus. +* La documentation API interactive automatique, avec 2 interfaces utilisateur au choix : * Swagger UI. * ReDoc. --- -Coming back to the previous code example, **FastAPI** will: +Pour revenir à l'exemple de code précédent, **FastAPI** permet de : -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. +* Valider que `item_id` existe dans le chemin des requêtes `GET` et `PUT`. +* Valider que `item_id` est de type `int` pour les requêtes `GET` et `PUT`. + * Si ce n'est pas le cas, le client voit une erreur utile et claire. +* Vérifier qu'il existe un paramètre de requête facultatif nommé `q` (comme dans `http://127.0.0.1:8000/items/foo?q=somequery`) pour les requêtes `GET`. + * Puisque le paramètre `q` est déclaré avec `= None`, il est facultatif. + * Sans le `None`, il serait nécessaire (comme l'est le corps de la requête dans le cas du `PUT`). +* Pour les requêtes `PUT` vers `/items/{item_id}`, de lire le corps en JSON : + * Vérifier qu'il a un attribut obligatoire `name` qui devrait être un `str`. + * Vérifier qu'il a un attribut obligatoire `prix` qui doit être un `float`. + * Vérifier qu'il a un attribut facultatif `is_offer`, qui devrait être un `bool`, s'il est présent. + * Tout cela fonctionnerait également pour les objets JSON profondément imbriqués. +* Convertir de et vers JSON automatiquement. +* Documenter tout avec OpenAPI, qui peut être utilisé par : + * Les systèmes de documentation interactifs. + * Les systèmes de génération automatique de code client, pour de nombreuses langues. +* Fournir directement 2 interfaces web de documentation interactive. --- -We just scratched the surface, but you already get the idea of how it all works. +Nous n'avons fait qu'effleurer la surface, mais vous avez déjà une idée de la façon dont tout cela fonctionne. -Try changing the line with: +Essayez de changer la ligne contenant : ```Python return {"item_name": item.name, "item_id": item_id} ``` -...from: +... de : ```Python ... "item_name": item.name ... ``` -...to: +... vers : ```Python ... "item_price": item.price ... ``` -...and see how your editor will auto-complete the attributes and know their types: +... et voyez comment votre éditeur complétera automatiquement les attributs et connaîtra leurs types : -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) +![compatibilité IDE](https://fastapi.tiangolo.com/img/vscode-completion.png) -For a more complete example including more features, see the Tutorial - User Guide. +Pour un exemple plus complet comprenant plus de fonctionnalités, voir le Tutoriel - Guide utilisateur. -**Spoiler alert**: the tutorial - user guide includes: +**Spoiler alert** : le tutoriel - guide utilisateur inclut : -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: +* Déclaration de **paramètres** provenant d'autres endroits différents comme : **en-têtes.**, **cookies**, **champs de formulaire** et **fichiers**. +* L'utilisation de **contraintes de validation** comme `maximum_length` ou `regex`. +* Un **systéme d'injection de dépendance ** très puissant et facile à utiliser . +* Sécurité et authentification, y compris la prise en charge de **OAuth2** avec les **jetons JWT** et l'authentification **HTTP Basic**. +* Des techniques plus avancées (mais tout aussi faciles) pour déclarer les **modèles JSON profondément imbriqués** (grâce à Pydantic). +* Intégration de **GraphQL** avec Strawberry et d'autres bibliothèques. +* D'obtenir de nombreuses fonctionnalités supplémentaires (grâce à Starlette) comme : * **WebSockets** - * **GraphQL** - * extremely easy tests based on `requests` and `pytest` - * **CORS** + * de tester le code très facilement avec `requests` et `pytest` + * **CORS** * **Cookie Sessions** - * ...and more. + * ... et plus encore. ## Performance -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) +Les benchmarks TechEmpower indépendants montrent que les applications **FastAPI** s'exécutant sous Uvicorn sont parmi les frameworks existants en Python les plus rapides , juste derrière Starlette et Uvicorn (utilisés en interne par FastAPI). (*) -To understand more about it, see the section Benchmarks. +Pour en savoir plus, consultez la section Benchmarks. -## Optional Dependencies +## Dépendances facultatives -Used by Pydantic: +Utilisées par Pydantic: -* ujson - for faster JSON "parsing". -* email_validator - for email validation. +* email_validator - pour la validation des adresses email. -Used by Starlette: +Utilisées par Starlette : -* requests - 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). -* graphene - Required for `GraphQLApp` support. -* ujson - Required if you want to use `UJSONResponse`. +* requests - Obligatoire si vous souhaitez utiliser `TestClient`. +* jinja2 - Obligatoire si vous souhaitez utiliser la configuration de template par defaut. +* python-multipart - Obligatoire si vous souhaitez supporter le "décodage" de formulaire avec `request.form()`. +* itsdangerous - Obligatoire pour la prise en charge de `SessionMiddleware`. +* pyyaml - Obligatoire pour le support `SchemaGenerator` de Starlette (vous n'en avez probablement pas besoin avec FastAPI). +* ujson - Obligatoire si vous souhaitez utiliser `UJSONResponse`. -Used by FastAPI / Starlette: +Utilisées par FastAPI / Starlette : -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. +* uvicorn - Pour le serveur qui charge et sert votre application. +* orjson - Obligatoire si vous voulez utiliser `ORJSONResponse`. -You can install all of these with `pip install fastapi[all]`. +Vous pouvez tout installer avec `pip install fastapi[all]`. -## License +## Licence -This project is licensed under the terms of the MIT license. +Ce projet est soumis aux termes de la licence MIT. diff --git a/docs/fr/docs/tutorial/debugging.md b/docs/fr/docs/tutorial/debugging.md new file mode 100644 index 0000000000..e58872d30a --- /dev/null +++ b/docs/fr/docs/tutorial/debugging.md @@ -0,0 +1,112 @@ +# Débogage + +Vous pouvez connecter le débogueur dans votre éditeur, par exemple avec Visual Studio Code ou PyCharm. + +## Faites appel à `uvicorn` + +Dans votre application FastAPI, importez et exécutez directement `uvicorn` : + +```Python hl_lines="1 15" +{!../../../docs_src/debugging/tutorial001.py!} +``` + +### À propos de `__name__ == "__main__"` + +Le but principal de `__name__ == "__main__"` est d'avoir du code qui est exécuté lorsque votre fichier est appelé avec : + +
+ +```console +$ python myapp.py +``` + +
+ +mais qui n'est pas appelé lorsqu'un autre fichier l'importe, comme dans : + +```Python +from myapp import app +``` + +#### Pour davantage de détails + +Imaginons que votre fichier s'appelle `myapp.py`. + +Si vous l'exécutez avec : + +
+ +```console +$ python myapp.py +``` + +
+ +alors la variable interne `__name__` de votre fichier, créée automatiquement par Python, aura pour valeur la chaîne de caractères `"__main__"`. + +Ainsi, la section : + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +va s'exécuter. + +--- + +Cela ne se produira pas si vous importez ce module (fichier). + +Par exemple, si vous avez un autre fichier `importer.py` qui contient : + +```Python +from myapp import app + +# Code supplémentaire +``` + +dans ce cas, la variable automatique `__name__` à l'intérieur de `myapp.py` n'aura pas la valeur `"__main__"`. + +Ainsi, la ligne : + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +ne sera pas exécutée. + +!!! info +Pour plus d'informations, consultez la documentation officielle de Python. + +## Exécutez votre code avec votre débogueur + +Parce que vous exécutez le serveur Uvicorn directement depuis votre code, vous pouvez appeler votre programme Python (votre application FastAPI) directement depuis le débogueur. + +--- + +Par exemple, dans Visual Studio Code, vous pouvez : + +- Cliquer sur l'onglet "Debug" de la barre d'activités de Visual Studio Code. +- "Add configuration...". +- Sélectionnez "Python". +- Lancez le débogueur avec l'option "`Python: Current File (Integrated Terminal)`". + +Il démarrera alors le serveur avec votre code **FastAPI**, s'arrêtera à vos points d'arrêt, etc. + +Voici à quoi cela pourrait ressembler : + + + +--- + +Si vous utilisez Pycharm, vous pouvez : + +- Ouvrir le menu "Run". +- Sélectionnez l'option "Debug...". +- Un menu contextuel s'affiche alors. +- Sélectionnez le fichier à déboguer (dans ce cas, `main.py`). + +Il démarrera alors le serveur avec votre code **FastAPI**, s'arrêtera à vos points d'arrêt, etc. + +Voici à quoi cela pourrait ressembler : + + diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index bf0d2b21cc..de18856f44 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -1,152 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/fr/ -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: fr -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -- Tutoriel - Guide utilisateur: - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/body.md - - tutorial/background-tasks.md -- async.md -- Déploiement: - - deployment/docker.md -- project-generation.md -- alternatives.md -- external-links.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/he/docs/index.md b/docs/he/docs/index.md new file mode 100644 index 0000000000..802dbe8b5d --- /dev/null +++ b/docs/he/docs/index.md @@ -0,0 +1,463 @@ +

+ FastAPI +

+

+ תשתית FastAPI, ביצועים גבוהים, קלה ללמידה, מהירה לתכנות, מוכנה לסביבת ייצור +

+

+ + Test + + + Coverage + + + Package version + + + Supported Python versions + +

+ +--- + +**תיעוד**: https://fastapi.tiangolo.com + +**קוד**: https://github.com/tiangolo/fastapi + +--- + +FastAPI היא תשתית רשת מודרנית ומהירה (ביצועים גבוהים) לבניית ממשקי תכנות יישומים (API) עם פייתון 3.6+ בהתבסס על רמזי טיפוסים סטנדרטיים. + +תכונות המפתח הן: + +- **מהירה**: ביצועים גבוהים מאוד, בקנה אחד עם NodeJS ו - Go (תודות ל - Starlette ו - Pydantic). [אחת מתשתיות הפייתון המהירות ביותר](#performance). + +- **מהירה לתכנות**: הגבירו את מהירות פיתוח התכונות החדשות בכ - %200 עד %300. \* +- **פחות שגיאות**: מנעו כ - %40 משגיאות אנוש (מפתחים). \* +- **אינטואיטיבית**: תמיכת עורך מעולה. השלמה בכל מקום. פחות זמן ניפוי שגיאות. +- **קלה**: מתוכננת להיות קלה לשימוש וללמידה. פחות זמן קריאת תיעוד. +- **קצרה**: מזערו שכפול קוד. מספר תכונות מכל הכרזת פרמטר. פחות שגיאות. +- **חסונה**: קבלו קוד מוכן לסביבת ייצור. עם תיעוד אינטרקטיבי אוטומטי. +- **מבוססת סטנדרטים**: מבוססת על (ותואמת לחלוטין ל -) הסטדנרטים הפתוחים לממשקי תכנות יישומים: OpenAPI (ידועים לשעבר כ - Swagger) ו - JSON Schema. + +\* הערכה מבוססת על בדיקות של צוות פיתוח פנימי שבונה אפליקציות בסביבת ייצור. + +## נותני חסות + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} + +{% endfor -%} +{%- for sponsor in sponsors.silver -%} + +{% endfor %} +{% endif %} + + + +נותני חסות אחרים + +## דעות + +"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" + +
Kabir Khan - Microsoft (ref)
+ +--- + +"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" + +
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
+ +--- + +"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" + +
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
+ +--- + +"_I’m over the moon excited about **FastAPI**. It’s so fun!_" + +
Brian Okken - Python Bytes podcast host (ref)
+ +--- + +"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" + +
Timothy Crosley - Hug creator (ref)
+ +--- + +"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" + +"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" + +
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
+ +--- + +## **Typer**, ה - FastAPI של ממשקי שורת פקודה (CLI). + + + +אם אתם בונים אפליקציית CLI לשימוש במסוף במקום ממשק רשת, העיפו מבט על **Typer**. + +**Typer** היא אחותה הקטנה של FastAPI. ומטרתה היא להיות ה - **FastAPI של ממשקי שורת פקודה**. ⌨️ 🚀 + +## תלויות + +פייתון 3.6+ + +FastAPI עומדת על כתפי ענקיות: + +- Starlette לחלקי הרשת. +- Pydantic לחלקי המידע. + +## התקנה + +
+ +```console +$ pip install fastapi + +---> 100% +``` + +
+ +תצטרכו גם שרת ASGI כגון Uvicorn או Hypercorn. + +
+ +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +
+ +## דוגמא + +### צרו אותה + +- צרו קובץ בשם `main.py` עם: + +```Python +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +
+או השתמשו ב - async def... + +אם הקוד שלכם משתמש ב - `async` / `await`, השתמשו ב - `async def`: + +```Python hl_lines="9 14" +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +**שימו לב**: + +אם אינכם יודעים, בדקו את פרק "ממהרים?" על `async` ו - `await` בתיעוד. + +
+ +### הריצו אותה + +התחילו את השרת עם: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +
+על הפקודה uvicorn main:app --reload... + +הפקודה `uvicorn main:app` מתייחסת ל: + +- `main`: הקובץ `main.py` (מודול פייתון). +- `app`: האובייקט שנוצר בתוך `main.py` עם השורה app = FastAPI(). +- --reload: גרמו לשרת להתאתחל לאחר שינויים בקוד. עשו זאת רק בסביבת פיתוח. + +
+ +### בדקו אותה + +פתחו את הדפדפן שלכם בכתובת http://127.0.0.1:8000/items/5?q=somequery. + +אתם תראו תגובת JSON: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +כבר יצרתם API ש: + +- מקבל בקשות HTTP בנתיבים `/` ו - /items/{item_id}. +- שני ה _נתיבים_ מקבלים _בקשות_ `GET` (ידועות גם כ*מתודות* HTTP). +- ה _נתיב_ /items/{item_id} כולל \*פרמטר נתיב\_ `item_id` שאמור להיות `int`. +- ה _נתיב_ /items/{item_id} \*פרמטר שאילתא\_ אופציונלי `q`. + +### תיעוד API אינטרקטיבי + +כעת פנו לכתובת http://127.0.0.1:8000/docs. + +אתם תראו את התיעוד האוטומטי (מסופק על ידי Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### תיעוד אלטרנטיבי + +כעת פנו לכתובת http://127.0.0.1:8000/redoc. + +אתם תראו תיעוד אלטרנטיבי (מסופק על ידי ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## שדרוג לדוגמא + +כעת ערכו את הקובץ `main.py` כך שיוכל לקבל גוף מבקשת `PUT`. + +הגדירו את הגוף בעזרת רמזי טיפוסים סטנדרטיים, הודות ל - `Pydantic`. + +```Python hl_lines="4 9-12 25-27" +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +השרת אמול להתאתחל אוטומטית (מאחר והוספתם --reload לפקודת `uvicorn` שלמעלה). + +### שדרוג התיעוד האינטרקטיבי + +כעת פנו לכתובת http://127.0.0.1:8000/docs. + +- התיעוד האוטומטי יתעדכן, כולל הגוף החדש: + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +- לחצו על הכפתור "Try it out", הוא יאפשר לכם למלא את הפרמטרים ולעבוד ישירות מול ה - API. + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) + +- אחר כך לחצו על הכפתור "Execute", האתר יתקשר עם ה - API שלכם, ישלח את הפרמטרים, ישיג את התוצאות ואז יראה אותן על המסך: + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) + +### שדרוג התיעוד האלטרנטיבי + +כעת פנו לכתובת http://127.0.0.1:8000/redoc. + +- התיעוד האלטרנטיבי גם יראה את פרמטר השאילתא והגוף החדשים. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### סיכום + +לסיכום, אתם מכריזים ** פעם אחת** על טיפוסי הפרמטרים, גוף וכו' כפרמטרים לפונקציה. + +אתם עושים את זה עם טיפוסי פייתון מודרניים. + +אתם לא צריכים ללמוד תחביר חדש, מתודות או מחלקות של ספרייה ספיציפית, וכו' + +רק **פייתון 3.6+** סטנדרטי. + +לדוגמא, ל - `int`: + +```Python +item_id: int +``` + +או למודל `Item` מורכב יותר: + +```Python +item: Item +``` + +...ועם הכרזת הטיפוס האחת הזו אתם מקבלים: + +- תמיכת עורך, כולל: + - השלמות. + - בדיקת טיפוסים. +- אימות מידע: + - שגיאות ברורות ואטומטיות כאשר מוכנס מידע לא חוקי . + - אימות אפילו לאובייקטי JSON מקוננים. +- המרה של מידע קלט: המרה של מידע שמגיע מהרשת למידע וטיפוסים של פייתון. קורא מ: + - JSON. + - פרמטרי נתיב. + - פרמטרי שאילתא. + - עוגיות. + - כותרות. + - טפסים. + - קבצים. +- המרה של מידע פלט: המרה של מידע וטיפוסים מפייתון למידע רשת (כ - JSON): + - המירו טיפוסי פייתון (`str`, `int`, `float`, `bool`, `list`, etc). + - עצמי `datetime`. + - עצמי `UUID`. + - מודלי בסיסי נתונים. + - ...ורבים אחרים. +- תיעוד API אוטומטי ואינטרקטיבית כולל שתי אלטרנטיבות לממשק המשתמש: + - Swagger UI. + - ReDoc. + +--- + +בחזרה לדוגמאת הקוד הקודמת, **FastAPI** ידאג: + +- לאמת שיש `item_id` בנתיב בבקשות `GET` ו - `PUT`. +- לאמת שה - `item_id` הוא מטיפוס `int` בבקשות `GET` ו - `PUT`. + - אם הוא לא, הלקוח יראה שגיאה ברורה ושימושית. +- לבדוק האם קיים פרמטר שאילתא בשם `q` (קרי `http://127.0.0.1:8000/items/foo?q=somequery`) לבקשות `GET`. + - מאחר והפרמטר `q` מוגדר עם = None, הוא אופציונלי. + - לולא ה - `None` הוא היה חובה (כמו הגוף במקרה של `PUT`). +- לבקשות `PUT` לנתיב /items/{item_id}, לקרוא את גוף הבקשה כ - JSON: + - לאמת שהוא כולל את מאפיין החובה `name` שאמור להיות מטיפוס `str`. + - לאמת שהוא כולל את מאפיין החובה `price` שחייב להיות מטיפוס `float`. + - לבדוק האם הוא כולל את מאפיין הרשות `is_offer` שאמור להיות מטיפוס `bool`, אם הוא נמצא. + - כל זה יעבוד גם לאובייקט JSON מקונן. +- להמיר מ - JSON ול- JSON אוטומטית. +- לתעד הכל באמצעות OpenAPI, תיעוד שבו יוכלו להשתמש: + - מערכות תיעוד אינטרקטיביות. + - מערכות ייצור קוד אוטומטיות, להרבה שפות. +- לספק ישירות שתי מערכות תיעוד רשתיות. + +--- + +רק גרדנו את קצה הקרחון, אבל כבר יש לכם רעיון של איך הכל עובד. + +נסו לשנות את השורה: + +```Python + return {"item_name": item.name, "item_id": item_id} +``` + +...מ: + +```Python + ... "item_name": item.name ... +``` + +...ל: + +```Python + ... "item_price": item.price ... +``` + +...וראו איך העורך שלכם משלים את המאפיינים ויודע את הטיפוסים שלהם: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +לדוגמא יותר שלמה שכוללת עוד תכונות, ראו את המדריך - למשתמש. + +**התראת ספוילרים**: המדריך - למשתמש כולל: + +- הכרזה על **פרמטרים** ממקורות אחרים ושונים כגון: **כותרות**, **עוגיות**, **טפסים** ו - **קבצים**. +- איך לקבוע **מגבלות אימות** בעזרת `maximum_length` או `regex`. +- דרך חזקה וקלה להשתמש ב**הזרקת תלויות**. +- אבטחה והתאמתות, כולל תמיכה ב - **OAuth2** עם **JWT** והתאמתות **HTTP Basic**. +- טכניקות מתקדמות (אבל קלות באותה מידה) להכרזת אובייקטי JSON מקוננים (תודות ל - Pydantic). +- אינטרקציה עם **GraphQL** דרך Strawberry וספריות אחרות. +- תכונות נוספות רבות (תודות ל - Starlette) כגון: + - **WebSockets** + - בדיקות קלות במיוחד מבוססות על `requests` ו - `pytest` + - **CORS** + - **Cookie Sessions** + - ...ועוד. + +## ביצועים + +בדיקות עצמאיות של TechEmpower הראו שאפליקציות **FastAPI** שרצות תחת Uvicorn הן מתשתיות הפייתון המהירות ביותר, רק מתחת ל - Starlette ו - Uvicorn עצמן (ש - FastAPI מבוססת עליהן). (\*) + +כדי להבין עוד על הנושא, ראו את הפרק Benchmarks. + +## תלויות אופציונליות + +בשימוש Pydantic: + +- email_validator - לאימות כתובות אימייל. + +בשימוש Starlette: + +- httpx - דרוש אם ברצונכם להשתמש ב - `TestClient`. +- jinja2 - דרוש אם ברצונכם להשתמש בברירת המחדל של תצורת הטמפלייטים. +- python-multipart - דרוש אם ברצונכם לתמוך ב "פרסור" טפסים, באצמעות request.form(). +- itsdangerous - דרוש אם ברצונכם להשתמש ב - `SessionMiddleware`. +- pyyaml - דרוש אם ברצונכם להשתמש ב - `SchemaGenerator` של Starlette (כנראה שאתם לא צריכים את זה עם FastAPI). +- ujson - דרוש אם ברצונכם להשתמש ב - `UJSONResponse`. + +בשימוש FastAPI / Starlette: + +- uvicorn - לשרת שטוען ומגיש את האפליקציה שלכם. +- orjson - דרוש אם ברצונכם להשתמש ב - `ORJSONResponse`. + +תוכלו להתקין את כל אלו באמצעות pip install "fastapi[all]". + +## רשיון + +הפרויקט הזה הוא תחת התנאים של רשיון MIT. diff --git a/docs/he/mkdocs.yml b/docs/he/mkdocs.yml new file mode 100644 index 0000000000..de18856f44 --- /dev/null +++ b/docs/he/mkdocs.yml @@ -0,0 +1 @@ +INHERIT: ../en/mkdocs.yml diff --git a/docs/id/docs/index.md b/docs/id/docs/index.md index 0bb7b55e3b..ed551f9102 100644 --- a/docs/id/docs/index.md +++ b/docs/id/docs/index.md @@ -111,7 +111,7 @@ If you are building a CLI app to be ## Requirements -Python 3.6+ +Python 3.7+ FastAPI stands on the shoulders of giants: @@ -135,7 +135,7 @@ You will also need an ASGI server, for production such as ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -426,7 +426,7 @@ For a more complete example including more features, see the ujson - for faster JSON "parsing". * email_validator - for email validation. Used by Starlette: -* requests - Required if you want to use the `TestClient`. +* 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. diff --git a/docs/id/docs/tutorial/index.md b/docs/id/docs/tutorial/index.md new file mode 100644 index 0000000000..b8ed96ae1f --- /dev/null +++ b/docs/id/docs/tutorial/index.md @@ -0,0 +1,80 @@ +# Tutorial - Pedoman Pengguna - Pengenalan + +Tutorial ini menunjukan cara menggunakan ***FastAPI*** dengan semua fitur-fiturnya, tahap demi tahap. + +Setiap bagian dibangun secara bertahap dari bagian sebelumnya, tetapi terstruktur untuk memisahkan banyak topik, sehingga kamu bisa secara langsung menuju ke topik spesifik untuk menyelesaikan kebutuhan API tertentu. + +Ini juga dibangun untuk digunakan sebagai referensi yang akan datang. + +Sehingga kamu dapat kembali lagi dan mencari apa yang kamu butuhkan dengan tepat. + +## Jalankan kode + +Semua blok-blok kode dapat disalin dan digunakan langsung (Mereka semua sebenarnya adalah file python yang sudah teruji). + +Untuk menjalankan setiap contoh, salin kode ke file `main.py`, dan jalankan `uvicorn` dengan: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +**SANGAT disarankan** agar kamu menulis atau menyalin kode, mengubahnya dan menjalankannya secara lokal. + +Dengan menggunakannya di dalam editor, benar-benar memperlihatkan manfaat dari FastAPI, melihat bagaimana sedikitnya kode yang harus kamu tulis, semua pengecekan tipe, pelengkapan otomatis, dll. + +--- + +## Install FastAPI + +Langkah pertama adalah dengan meng-install FastAPI. + +Untuk tutorial, kamu mungkin hendak meng-installnya dengan semua pilihan fitur dan dependensinya: + +
+ +```console +$ pip install "fastapi[all]" + +---> 100% +``` + +
+ +...yang juga termasuk `uvicorn`, yang dapat kamu gunakan sebagai server yang menjalankan kodemu. + +!!! catatan + Kamu juga dapat meng-installnya bagian demi bagian. + + Hal ini mungkin yang akan kamu lakukan ketika kamu hendak menyebarkan (men-deploy) aplikasimu ke tahap produksi: + + ``` + pip install fastapi + ``` + + Juga install `uvicorn` untuk menjalankan server" + + ``` + pip install "uvicorn[standard]" + ``` + + Dan demikian juga untuk pilihan dependensi yang hendak kamu gunakan. + +## Pedoman Pengguna Lanjutan + +Tersedia juga **Pedoman Pengguna Lanjutan** yang dapat kamu baca nanti setelah **Tutorial - Pedoman Pengguna** ini. + +**Pedoman Pengguna Lanjutan**, dibangun atas hal ini, menggunakan konsep yang sama, dan mengajarkan kepadamu beberapa fitur tambahan. + +Tetapi kamu harus membaca terlebih dahulu **Tutorial - Pedoman Pengguna** (apa yang sedang kamu baca sekarang). + +Hal ini dirancang supaya kamu dapat membangun aplikasi lengkap dengan hanya **Tutorial - Pedoman Pengguna**, dan kemudian mengembangkannya ke banyak cara yang berbeda, tergantung dari kebutuhanmu, menggunakan beberapa ide-ide tambahan dari **Pedoman Pengguna Lanjutan**. diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml index 769547d11a..de18856f44 100644 --- a/docs/id/mkdocs.yml +++ b/docs/id/mkdocs.yml @@ -1,137 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/id/ -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: id -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - 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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md deleted file mode 100644 index 6acf925528..0000000000 --- a/docs/it/docs/index.md +++ /dev/null @@ -1,463 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Build Status - - - Coverage - - - Package version - -

- ---- - -**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.6+ 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 -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.6+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install uvicorn[standard] - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```Python -from fastapi import FastAPI -from typing import Optional - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: str = Optional[None]): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="7 12" -from fastapi import FastAPI -from typing import Optional - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="2 7-10 23-25" -from fastapi import FastAPI -from pydantic import BaseModel -from typing import Optional - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: bool = Optional[None] - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on `requests` and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* requests - 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). -* graphene - Required for `GraphQLApp` support. -* 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/it/mkdocs.yml b/docs/it/mkdocs.yml deleted file mode 100644 index ebec9a6429..0000000000 --- a/docs/it/mkdocs.yml +++ /dev/null @@ -1,137 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/it/ -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: it -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - 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 -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: /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: /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/ja/docs/advanced/additional-status-codes.md b/docs/ja/docs/advanced/additional-status-codes.md index 6c03cd92b3..d1f8e64516 100644 --- a/docs/ja/docs/advanced/additional-status-codes.md +++ b/docs/ja/docs/advanced/additional-status-codes.md @@ -14,7 +14,7 @@ これを達成するには、 `JSONResponse` をインポートし、 `status_code` を設定して直接内容を返します。 -```Python hl_lines="4 23" +```Python hl_lines="4 25" {!../../../docs_src/additional_status_codes/tutorial001.py!} ``` diff --git a/docs/ja/docs/advanced/index.md b/docs/ja/docs/advanced/index.md new file mode 100644 index 0000000000..0732fc405a --- /dev/null +++ b/docs/ja/docs/advanced/index.md @@ -0,0 +1,24 @@ +# 高度なユーザーガイド + +## さらなる機能 + +[チュートリアル - ユーザーガイド](../tutorial/){.internal-link target=_blank}により、**FastAPI**の主要な機能は十分に理解できたことでしょう。 + +以降のセクションでは、チュートリアルでは説明しきれなかったオプションや設定、および機能について説明します。 + +!!! tip "豆知識" + 以降のセクションは、 **必ずしも"応用編"ではありません**。 + + ユースケースによっては、その中から解決策を見つけられるかもしれません。 + +## 先にチュートリアルを読む + +[チュートリアル - ユーザーガイド](../tutorial/){.internal-link target=_blank}の知識があれば、**FastAPI**の主要な機能を利用することができます。 + +以降のセクションは、すでにチュートリアルを読んで、その主要なアイデアを理解できていることを前提としています。 + +## テスト駆動開発のコース + +このセクションの内容を補完するために脱初心者用コースを受けたい場合は、**TestDriven.io**による、Test-Driven Development with FastAPI and Dockerを確認するのがよいかもしれません。 + +現在、このコースで得られた利益の10%が**FastAPI**の開発のために寄付されています。🎉 😄 diff --git a/docs/ja/docs/advanced/nosql-databases.md b/docs/ja/docs/advanced/nosql-databases.md new file mode 100644 index 0000000000..fbd76e96b1 --- /dev/null +++ b/docs/ja/docs/advanced/nosql-databases.md @@ -0,0 +1,156 @@ +# NoSQL (分散 / ビッグデータ) Databases + +**FastAPI** はあらゆる NoSQLと統合することもできます。 + +ここではドキュメントベースのNoSQLデータベースである**Couchbase**を使用した例を見てみましょう。 + +他にもこれらのNoSQLデータベースを利用することが出来ます: + +* **MongoDB** +* **Cassandra** +* **CouchDB** +* **ArangoDB** +* **ElasticSearch** など。 + +!!! tip "豆知識" + **FastAPI**と**Couchbase**を使った公式プロジェクト・ジェネレータがあります。すべて**Docker**ベースで、フロントエンドやその他のツールも含まれています: https://github.com/tiangolo/full-stack-fastapi-couchbase + +## Couchbase コンポーネントの Import + +まずはImportしましょう。今はその他のソースコードに注意を払う必要はありません。 + +```Python hl_lines="3-5" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## "document type" として利用する定数の定義 + +documentで利用する固定の`type`フィールドを用意しておきます。 + +これはCouchbaseで必須ではありませんが、後々の助けになるベストプラクティスです。 + +```Python hl_lines="9" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## `Bucket` を取得する関数の追加 + +**Couchbase**では、bucketはdocumentのセットで、様々な種類のものがあります。 + +Bucketは通常、同一のアプリケーション内で互いに関係を持っています。 + +リレーショナルデータベースの世界でいう"database"(データベースサーバではなく特定のdatabase)と類似しています。 + +**MongoDB** で例えると"collection"と似た概念です。 + +次のコードでは主に `Bucket` を利用してCouchbaseを操作します。 + +この関数では以下の処理を行います: + +* **Couchbase** クラスタ(1台の場合もあるでしょう)に接続 + * タイムアウトのデフォルト値を設定 +* クラスタで認証を取得 +* `Bucket` インスタンスを取得 + * タイムアウトのデフォルト値を設定 +* 作成した`Bucket`インスタンスを返却 + +```Python hl_lines="12-21" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## Pydantic モデルの作成 + +**Couchbase**のdocumentは実際には単にJSONオブジェクトなのでPydanticを利用してモデルに出来ます。 + +### `User` モデル + +まずは`User`モデルを作成してみましょう: + +```Python hl_lines="24-28" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +このモデルは*path operation*に使用するので`hashed_password`は含めません。 + +### `UserInDB` モデル + +それでは`UserInDB`モデルを作成しましょう。 + +こちらは実際にデータベースに保存されるデータを保持します。 + +`User`モデルの持つ全ての属性に加えていくつかの属性を追加するのでPydanticの`BaseModel`を継承せずに`User`のサブクラスとして定義します: + +```Python hl_lines="31-33" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +!!! note "備考" + データベースに保存される`hashed_password`と`type`フィールドを`UserInDB`モデルに保持させていることに注意してください。 + + しかしこれらは(*path operation*で返却する)一般的な`User`モデルには含まれません + +## user の取得 + +それでは次の関数を作成しましょう: + +* username を取得する +* username を利用してdocumentのIDを生成する +* 作成したIDでdocumentを取得する +* documentの内容を`UserInDB`モデルに設定する + +*path operation関数*とは別に、`username`(またはその他のパラメータ)からuserを取得することだけに特化した関数を作成することで、より簡単に複数の部分で再利用したりユニットテストを追加することができます。 + +```Python hl_lines="36-42" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +### f-strings + +`f"userprofile::{username}"` という記載に馴染みがありませんか?これは Python の"f-string"と呼ばれるものです。 + +f-stringの`{}`の中に入れられた変数は、文字列の中に展開/注入されます。 + +### `dict` アンパック + +`UserInDB(**result.value)`という記載に馴染みがありませんか?これは`dict`の"アンパック"と呼ばれるものです。 + +これは`result.value`の`dict`からそのキーと値をそれぞれ取りキーワード引数として`UserInDB`に渡します。 + +例えば`dict`が下記のようになっていた場合: + +```Python +{ + "username": "johndoe", + "hashed_password": "some_hash", +} +``` + +`UserInDB`には次のように渡されます: + +```Python +UserInDB(username="johndoe", hashed_password="some_hash") +``` + +## **FastAPI** コードの実装 + +### `FastAPI` app の作成 + +```Python hl_lines="46" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +### *path operation関数*の作成 + +私たちのコードはCouchbaseを呼び出しており、実験的なPython awaitを使用していないので、私たちは`async def`ではなく通常の`def`で関数を宣言する必要があります。 + +また、Couchbaseは単一の`Bucket`オブジェクトを複数のスレッドで使用しないことを推奨していますので、単に直接Bucketを取得して関数に渡すことが出来ます。 + +```Python hl_lines="49-53" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## まとめ + +他のサードパーティ製のNoSQLデータベースを利用する場合でも、そのデータベースの標準ライブラリを利用するだけで利用できます。 + +他の外部ツール、システム、APIについても同じことが言えます。 diff --git a/docs/ja/docs/advanced/websockets.md b/docs/ja/docs/advanced/websockets.md new file mode 100644 index 0000000000..65e4112a6b --- /dev/null +++ b/docs/ja/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSocket + +**FastAPI**でWebSocketが使用できます。 + +## `WebSockets`のインストール + +まず `WebSockets`のインストールが必要です。 + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## WebSocket クライアント + +### 本番環境 + +本番環境では、React、Vue.js、Angularなどの最新のフレームワークで作成されたフロントエンドを使用しているでしょう。 + +そして、バックエンドとWebSocketを使用して通信するために、おそらくフロントエンドのユーティリティを使用することになるでしょう。 + +または、ネイティブコードでWebSocketバックエンドと直接通信するネイティブモバイルアプリケーションがあるかもしれません。 + +他にも、WebSocketのエンドポイントと通信する方法があるかもしれません。 + +--- + +ただし、この例では非常にシンプルなHTML文書といくつかのJavaScriptを、すべてソースコードの中に入れて使用することにします。 + +もちろん、これは最適な方法ではありませんし、本番環境で使うことはないでしょう。 + +本番環境では、上記の方法のいずれかの選択肢を採用することになるでしょう。 + +しかし、これはWebSocketのサーバーサイドに焦点を当て、実用的な例を示す最も簡単な方法です。 + +```Python hl_lines="2 6-38 41-43" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +## `websocket` を作成する + +**FastAPI** アプリケーションで、`websocket` を作成します。 + +```Python hl_lines="1 46-47" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +!!! note "技術詳細" + `from starlette.websockets import WebSocket` を使用しても構いません. + + **FastAPI** は開発者の利便性のために、同じ `WebSocket` を提供します。しかし、こちらはStarletteから直接提供されるものです。 + +## メッセージの送受信 + +WebSocketルートでは、 `await` を使ってメッセージの送受信ができます。 + +```Python hl_lines="48-52" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +バイナリやテキストデータ、JSONデータを送受信できます。 + +## 試してみる + +ファイル名が `main.py` である場合、以下の方法でアプリケーションを実行します。 + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +ブラウザで http://127.0.0.1:8000 を開きます。 + +次のようなシンプルなページが表示されます。 + + + +入力ボックスにメッセージを入力して送信できます。 + + + +そして、 WebSocketを使用した**FastAPI**アプリケーションが応答します。 + + + +複数のメッセージを送信(および受信)できます。 + + + +そして、これらの通信はすべて同じWebSocket接続を使用します。 + +## 依存関係 + +WebSocketエンドポイントでは、`fastapi` から以下をインポートして使用できます。 + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +これらは、他のFastAPI エンドポイント/*path operation* の場合と同じように機能します。 + +```Python hl_lines="58-65 68-83" +{!../../../docs_src/websockets/tutorial002.py!} +``` + +!!! info "情報" + WebSocket で `HTTPException` を発生させることはあまり意味がありません。したがって、WebSocketの接続を直接閉じる方がよいでしょう。 + + クロージングコードは、仕様で定義された有効なコードの中から使用することができます。 + + 将来的には、どこからでも `raise` できる `WebSocketException` が用意され、専用の例外ハンドラを追加できるようになる予定です。これは、Starlette の PR #527 に依存するものです。 + +### 依存関係を用いてWebSocketsを試してみる + +ファイル名が `main.py` である場合、以下の方法でアプリケーションを実行します。 + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +ブラウザで http://127.0.0.1:8000 を開きます。 + +クライアントが設定できる項目は以下の通りです。 + +* パスで使用される「Item ID」 +* クエリパラメータとして使用される「Token」 + +!!! tip "豆知識" + クエリ `token` は依存パッケージによって処理されることに注意してください。 + +これにより、WebSocketに接続してメッセージを送受信できます。 + + + +## 切断や複数クライアントへの対応 + +WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。 + +```Python hl_lines="81-83" +{!../../../docs_src/websockets/tutorial003.py!} +``` + +試してみるには、 + +* いくつかのブラウザタブでアプリを開きます。 +* それらのタブでメッセージを記入してください。 +* そして、タブのうち1つを閉じてください。 + +これにより例外 `WebSocketDisconnect` が発生し、他のすべてのクライアントは次のようなメッセージを受信します。 + +``` +Client #1596980209979 left the chat +``` + +!!! tip "豆知識" + 上記のアプリは、複数の WebSocket 接続に対してメッセージを処理し、ブロードキャストする方法を示すための最小限のシンプルな例です。 + + しかし、すべての接続がメモリ内の単一のリストで処理されるため、プロセスの実行中にのみ機能し、単一のプロセスでのみ機能することに注意してください。 + + もしFastAPIと簡単に統合できて、RedisやPostgreSQLなどでサポートされている、より堅牢なものが必要なら、encode/broadcaster を確認してください。 + +## その他のドキュメント + +オプションの詳細については、Starletteのドキュメントを確認してください。 + +* `WebSocket` クラス +* クラスベースのWebSocket処理 diff --git a/docs/ja/docs/contributing.md b/docs/ja/docs/contributing.md index 07e53eeb70..31db51c52b 100644 --- a/docs/ja/docs/contributing.md +++ b/docs/ja/docs/contributing.md @@ -88,62 +88,29 @@ $ python -m venv env !!! tip "豆知識" この環境で`pip`を使って新しいパッケージをインストールするたびに、仮想環境を再度有効化します。 - これにより、そのパッケージによってインストールされたターミナルのプログラム (`flit`など) を使用する場合、ローカル環境のものを使用し、グローバルにインストールされたものは使用されなくなります。 + これにより、そのパッケージによってインストールされたターミナルのプログラム を使用する場合、ローカル環境のものを使用し、グローバルにインストールされたものは使用されなくなります。 -### Flit +### pip -**FastAPI**はFlit を使って、ビルド、パッケージ化、公開します。 - -上記のように環境を有効化した後、`flit`をインストールします: +上記のように環境を有効化した後:
```console -$ pip install flit +$ pip install -r requirements.txt ---> 100% ```
- -次に、環境を再び有効化して、インストールしたばかりの`flit` (グローバルではない) を使用していることを確認します。 - -そして、`flit`を使用して開発のための依存関係をインストールします: - -=== "Linux, macOS" - -
- - ```console - $ flit install --deps develop --symlink - - ---> 100% - ``` - -
- -=== "Windows" - - Windowsユーザーは、`--symlink`のかわりに`--pth-file`を使用します: - -
- - ```console - $ flit install --deps develop --pth-file - - ---> 100% - ``` - -
- これで、すべての依存関係とFastAPIを、ローカル環境にインストールします。 #### ローカル環境でFastAPIを使う FastAPIをインポートして使用するPythonファイルを作成し、ローカル環境で実行すると、ローカルのFastAPIソースコードが使用されます。 -そして、`--symlink` (Windowsでは` --pth-file`) でインストールされているローカルのFastAPIソースコードを更新した場合、そのPythonファイルを再度実行すると、更新したばかりの新しいバージョンのFastAPIが使用されます。 +そして、`-e` でインストールされているローカルのFastAPIソースコードを更新した場合、そのPythonファイルを再度実行すると、更新したばかりの新しいバージョンのFastAPIが使用されます。 これにより、ローカルバージョンを「インストール」しなくても、すべての変更をテストできます。 @@ -161,7 +128,7 @@ $ bash scripts/format.sh また、すべてのインポートを自動でソートします。 -正しく並べ替えるには、上記セクションのコマンドで `--symlink` (Windowsの場合は` --pth-file`) を使い、FastAPIをローカル環境にインストールしている必要があります。 +正しく並べ替えるには、上記セクションのコマンドで `-e` を使い、FastAPIをローカル環境にインストールしている必要があります。 ### インポートの整形 diff --git a/docs/ja/docs/deployment/index.md b/docs/ja/docs/deployment/index.md index 40710a93a1..897956e38f 100644 --- a/docs/ja/docs/deployment/index.md +++ b/docs/ja/docs/deployment/index.md @@ -1,4 +1,4 @@ -# デプロイ - イントロ +# デプロイ **FastAPI** 製のアプリケーションは比較的容易にデプロイできます。 diff --git a/docs/ja/docs/deployment/manually.md b/docs/ja/docs/deployment/manually.md index dd4b568bdc..67010a66f4 100644 --- a/docs/ja/docs/deployment/manually.md +++ b/docs/ja/docs/deployment/manually.md @@ -11,7 +11,7 @@
```console - $ pip install uvicorn[standard] + $ pip install "uvicorn[standard]" ---> 100% ``` diff --git a/docs/ja/docs/features.md b/docs/ja/docs/features.md index 5ea68515da..a40b48cf0e 100644 --- a/docs/ja/docs/features.md +++ b/docs/ja/docs/features.md @@ -169,7 +169,7 @@ FastAPIには非常に使いやすく、非常に強力な ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -145,7 +145,7 @@ $ pip install uvicorn[standard] * `main.py` 파일을 만드십시오: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -158,7 +158,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -168,7 +168,7 @@ def read_item(item_id: int, q: Optional[str] = None): 여러분의 코드가 `async` / `await`을 사용한다면, `async def`를 사용하십시오. ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -181,7 +181,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -260,7 +260,7 @@ INFO: Application startup complete. Pydantic을 이용해 파이썬 표준 타입으로 본문을 선언합니다. ```Python hl_lines="4 9 10 11 12 25 26 27" -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -271,7 +271,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -280,7 +280,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -422,7 +422,7 @@ item: Item * (Starlette 덕분에) 많은 추가 기능: * **웹 소켓** * **GraphQL** - * `requests` 및 `pytest`에 기반한 극히 쉬운 테스트 + * HTTPX 및 `pytest`에 기반한 극히 쉬운 테스트 * **CORS** * **쿠키 세션** * ...기타 등등. @@ -437,12 +437,11 @@ item: Item Pydantic이 사용하는: -* ujson - 더 빠른 JSON "파싱". * email_validator - 이메일 유효성 검사. Starlette이 사용하는: -* requests - `TestClient`를 사용하려면 필요. +* HTTPX - `TestClient`를 사용하려면 필요. * jinja2 - 기본 템플릿 설정을 사용하려면 필요. * python-multipart - `request.form()`과 함께 "parsing"의 지원을 원하면 필요. * itsdangerous - `SessionMiddleware` 지원을 위해 필요. diff --git a/docs/ko/docs/tutorial/cors.md b/docs/ko/docs/tutorial/cors.md new file mode 100644 index 0000000000..39e9ea83f1 --- /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/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md new file mode 100644 index 0000000000..bbf3a82838 --- /dev/null +++ b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md @@ -0,0 +1,244 @@ +# 의존성으로서의 클래스 + +**의존성 주입** 시스템에 대해 자세히 살펴보기 전에 이전 예제를 업그레이드 해보겠습니다. + +## 이전 예제의 `딕셔너리` + +이전 예제에서, 우리는 의존성(의존 가능한) 함수에서 `딕셔너리`객체를 반환하고 있었습니다: + +=== "파이썬 3.6 이상" + + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial001.py!} + ``` + +=== "파이썬 3.10 이상" + + ```Python hl_lines="7" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +우리는 *경로 작동 함수*의 매개변수 `commons`에서 `딕셔너리` 객체를 얻습니다. + +그리고 우리는 에디터들이 `딕셔너리` 객체의 키나 밸류의 자료형을 알 수 없기 때문에 자동 완성과 같은 기능을 제공해 줄 수 없다는 것을 알고 있습니다. + +더 나은 방법이 있을 것 같습니다... + +## 의존성으로 사용 가능한 것 + +지금까지 함수로 선언된 의존성을 봐왔습니다. + +아마도 더 일반적이기는 하겠지만 의존성을 선언하는 유일한 방법은 아닙니다. + +핵심 요소는 의존성이 "호출 가능"해야 한다는 것입니다 + +파이썬에서의 "**호출 가능**"은 파이썬이 함수처럼 "호출"할 수 있는 모든 것입니다. + +따라서, 만약 당신이 `something`(함수가 아닐 수도 있음) 객체를 가지고 있고, + +```Python +something() +``` + +또는 + +```Python +something(some_argument, some_keyword_argument="foo") +``` + +상기와 같은 방식으로 "호출(실행)" 할 수 있다면 "호출 가능"이 됩니다. + +## 의존성으로서의 클래스 + +파이썬 클래스의 인스턴스를 생성하기 위해 사용하는 것과 동일한 문법을 사용한다는 걸 알 수 있습니다. + +예를 들어: + +```Python +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +이 경우에 `fluffy`는 클래스 `Cat`의 인스턴스입니다. 그리고 우리는 `fluffy`를 만들기 위해서 `Cat`을 "호출"했습니다. + +따라서, 파이썬 클래스는 **호출 가능**합니다. + +그래서 **FastAPI**에서는 파이썬 클래스를 의존성으로 사용할 수 있습니다. + +FastAPI가 실질적으로 확인하는 것은 "호출 가능성"(함수, 클래스 또는 다른 모든 것)과 정의된 매개변수들입니다. + +"호출 가능"한 것을 의존성으로서 **FastAPI**에 전달하면, 그 "호출 가능"한 것의 매개변수들을 분석한 후 이를 *경로 동작 함수*를 위한 매개변수와 동일한 방식으로 처리합니다. 하위-의존성 또한 같은 방식으로 처리합니다. + +매개변수가 없는 "호출 가능"한 것 역시 매개변수가 없는 *경로 동작 함수*와 동일한 방식으로 적용됩니다. + +그래서, 우리는 위 예제에서의 `common_paramenters` 의존성을 클래스 `CommonQueryParams`로 바꿀 수 있습니다. + +=== "파이썬 3.6 이상" + + ```Python hl_lines="11-15" + {!> ../../../docs_src/dependencies/tutorial002.py!} + ``` + +=== "파이썬 3.10 이상" + + ```Python hl_lines="9-13" + {!> ../../../docs_src/dependencies/tutorial002_py310.py!} + ``` + +클래스의 인스턴스를 생성하는 데 사용되는 `__init__` 메서드에 주목하기 바랍니다: + +=== "파이썬 3.6 이상" + + ```Python hl_lines="12" + {!> ../../../docs_src/dependencies/tutorial002.py!} + ``` + +=== "파이썬 3.10 이상" + + ```Python hl_lines="10" + {!> ../../../docs_src/dependencies/tutorial002_py310.py!} + ``` + +...이전 `common_parameters`와 동일한 매개변수를 가집니다: + +=== "파이썬 3.6 이상" + + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial001.py!} + ``` + +=== "파이썬 3.10 이상" + + ```Python hl_lines="6" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +이 매개변수들은 **FastAPI**가 의존성을 "해결"하기 위해 사용할 것입니다 + +함수와 클래스 두 가지 방식 모두, 아래 요소를 갖습니다: + +* `문자열`이면서 선택사항인 쿼리 매개변수 `q`. +* 기본값이 `0`이면서 `정수형`인 쿼리 매개변수 `skip` +* 기본값이 `100`이면서 `정수형`인 쿼리 매개변수 `limit` + +두 가지 방식 모두, 데이터는 변환, 검증되고 OpenAPI 스키마에 문서화됩니다. + +## 사용해봅시다! + +이제 아래의 클래스를 이용해서 의존성을 정의할 수 있습니다. + +=== "파이썬 3.6 이상" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial002.py!} + ``` + +=== "파이썬 3.10 이상" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial002_py310.py!} + ``` + +**FastAPI**는 `CommonQueryParams` 클래스를 호출합니다. 이것은 해당 클래스의 "인스턴스"를 생성하고 그 인스턴스는 함수의 매개변수 `commons`로 전달됩니다. + +## 타입 힌팅 vs `Depends` + +위 코드에서 `CommonQueryParams`를 두 번 작성한 방식에 주목하십시오: + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +마지막 `CommonQueryParams` 변수를 보면: + +```Python +... = Depends(CommonQueryParams) +``` + +... **FastAPI**가 실제로 어떤 것이 의존성인지 알기 위해서 사용하는 방법입니다. +FastAPI는 선언된 매개변수들을 추출할 것이고 실제로 이 변수들을 호출할 것입니다. + +--- + +이 경우에, 첫번째 `CommonQueryParams` 변수를 보면: + +```Python +commons: CommonQueryParams ... +``` + +... **FastAPI**는 `CommonQueryParams` 변수에 어떠한 특별한 의미도 부여하지 않습니다. FastAPI는 이 변수를 데이터 변환, 검증 등에 활용하지 않습니다. (활용하려면 `= Depends(CommonQueryParams)`를 사용해야 합니다.) + +사실 아래와 같이 작성해도 무관합니다: + +```Python +commons = Depends(CommonQueryParams) +``` + +..전체적인 코드는 아래와 같습니다: + +=== "파이썬 3.6 이상" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial003.py!} + ``` + +=== "파이썬 3.10 이상" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial003_py310.py!} + ``` + +그러나 자료형을 선언하면 에디터가 매개변수 `commons`로 전달될 것이 무엇인지 알게 되고, 이를 통해 코드 완성, 자료형 확인 등에 도움이 될 수 있으므로 권장됩니다. + + + +## 코드 단축 + +그러나 여기 `CommonQueryParams`를 두 번이나 작성하는, 코드 반복이 있다는 것을 알 수 있습니다: + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +**FastAPI**는 *특히* 의존성이 **FastAPI**가 클래스 자체의 인스턴스를 생성하기 위해 "호출"하는 클래스인 경우, 조금 더 쉬운 방법을 제공합니다. + +이러한 특정한 경우에는 아래처럼 사용할 수 있습니다: + +이렇게 쓰는 것 대신: + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +...이렇게 쓸 수 있습니다.: + +```Python +commons: CommonQueryParams = Depends() +``` + +의존성을 매개변수의 타입으로 선언하는 경우 `Depends(CommonQueryParams)`처럼 클래스 이름 전체를 *다시* 작성하는 대신, 매개변수를 넣지 않은 `Depends()`의 형태로 사용할 수 있습니다. + +아래에 같은 예제가 있습니다: + +=== "파이썬 3.6 이상" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial004.py!} + ``` + +=== "파이썬 3.10 이상" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial004_py310.py!} + ``` + +...이렇게 코드를 단축하여도 **FastAPI**는 무엇을 해야하는지 알고 있습니다. + +!!! tip "팁" + 만약 이것이 도움이 되기보다 더 헷갈리게 만든다면, 잊어버리십시오. 이것이 반드시 필요한 것은 아닙니다. + + 이것은 단지 손쉬운 방법일 뿐입니다. 왜냐하면 **FastAPI**는 코드 반복을 최소화할 수 있는 방법을 고민하기 때문입니다. diff --git a/docs/ko/docs/tutorial/index.md b/docs/ko/docs/tutorial/index.md index 622aad1aa6..deb5ca8f27 100644 --- a/docs/ko/docs/tutorial/index.md +++ b/docs/ko/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# 자습서 - 사용자 안내서 - 도입부 +# 자습서 - 사용자 안내서 이 자습서는 **FastAPI**의 대부분의 기능을 단계별로 사용하는 방법을 보여줍니다. @@ -43,7 +43,7 @@ $ uvicorn main:app --reload
```console -$ pip install fastapi[all] +$ pip install "fastapi[all]" ---> 100% ``` diff --git a/docs/ko/docs/tutorial/path-params-numeric-validations.md b/docs/ko/docs/tutorial/path-params-numeric-validations.md index abb9d03dbb..cadf543fc6 100644 --- a/docs/ko/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ko/docs/tutorial/path-params-numeric-validations.md @@ -43,7 +43,7 @@ 따라서 함수를 다음과 같이 선언 할 수 있습니다: -```Python hl_lines="8" +```Python hl_lines="7" {!../../../docs_src/path_params_numeric_validations/tutorial002.py!} ``` @@ -55,7 +55,7 @@ 파이썬은 `*`으로 아무런 행동도 하지 않지만, 따르는 매개변수들은 kwargs로도 알려진 키워드 인자(키-값 쌍)여야 함을 인지합니다. 기본값을 가지고 있지 않더라도 그렇습니다. -```Python hl_lines="8" +```Python hl_lines="7" {!../../../docs_src/path_params_numeric_validations/tutorial003.py!} ``` diff --git a/docs/ko/docs/tutorial/query-params.md b/docs/ko/docs/tutorial/query-params.md index 05f2ff9c91..bb631e6ffc 100644 --- a/docs/ko/docs/tutorial/query-params.md +++ b/docs/ko/docs/tutorial/query-params.md @@ -75,7 +75,7 @@ http://127.0.0.1:8000/items/?skip=20 !!! note "참고" FastAPI는 `q`가 `= None`이므로 선택적이라는 것을 인지합니다. - `Optional[str]`에 있는 `Optional`은 FastAPI(FastAPI는 `str` 부분만 사용합니다)가 사용하는게 아니지만, `Optional[str]`은 편집기에게 코드에서 오류를 찾아낼 수 있게 도와줍니다. + `Union[str, None]`에 있는 `Union`은 FastAPI(FastAPI는 `str` 부분만 사용합니다)가 사용하는게 아니지만, `Union[str, None]`은 편집기에게 코드에서 오류를 찾아낼 수 있게 도와줍니다. ## 쿼리 매개변수 형변환 diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml index 5bdcc73777..de18856f44 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -1,149 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/ko/ -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- 자습서 - 사용자 안내서: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/header-params.md - - tutorial/path-params-numeric-validations.md - - tutorial/response-status-code.md - - tutorial/request-files.md - - tutorial/request-forms-and-files.md -- 심화된 사용자 안내서: - - advanced/behind-a-proxy.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/nl/docs/index.md b/docs/nl/docs/index.md deleted file mode 100644 index 0070de1796..0000000000 --- a/docs/nl/docs/index.md +++ /dev/null @@ -1,468 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**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.6+ 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 -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.6+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```Python -from typing import Optional - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="9 14" -from typing import Optional - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Optional[bool] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with Strawberry and other libraries. -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on `requests` and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* requests - 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/nl/mkdocs.yml b/docs/nl/mkdocs.yml deleted file mode 100644 index 9cd1e04010..0000000000 --- a/docs/nl/mkdocs.yml +++ /dev/null @@ -1,137 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/nl/ -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: nl -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - 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 -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: /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: /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/pl/docs/features.md b/docs/pl/docs/features.md new file mode 100644 index 0000000000..49d362dd98 --- /dev/null +++ b/docs/pl/docs/features.md @@ -0,0 +1,200 @@ +# Cechy + +## Cechy FastAPI + +**FastAPI** zapewnia Ci następujące korzyści: + +### Oparcie o standardy open + +* OpenAPI do tworzenia API, w tym deklaracji ścieżek operacji, parametrów, ciał zapytań, bezpieczeństwa, itp. +* Automatyczna dokumentacja modelu danych za pomocą JSON Schema (ponieważ OpenAPI bazuje na JSON Schema). +* Zaprojektowane z myślą o zgodności z powyższymi standardami zamiast dodawania ich obsługi po fakcie. +* Możliwość automatycznego **generowania kodu klienta** w wielu językach. + +### Automatyczna dokumentacja + +Interaktywna dokumentacja i webowe interfejsy do eksploracji API. Z racji tego, że framework bazuje na OpenAPI, istnieje wiele opcji, z czego 2 są domyślnie dołączone. + +* Swagger UI, z interaktywnym interfejsem - odpytuj i testuj swoje API bezpośrednio z przeglądarki. + +![Swagger UI interakcja](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* Alternatywna dokumentacja API z ReDoc. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### Nowoczesny Python + +Wszystko opiera się na standardowych deklaracjach typu **Python 3.6** (dzięki Pydantic). Brak nowej składni do uczenia. Po prostu standardowy, współczesny Python. + +Jeśli potrzebujesz szybkiego przypomnienia jak używać deklaracji typów w Pythonie (nawet jeśli nie używasz FastAPI), sprawdź krótki samouczek: [Python Types](python-types.md){.internal-link target=_blank}. + +Wystarczy, że napiszesz standardowe deklaracje typów Pythona: + +```Python +from datetime import date + +from pydantic import BaseModel + +# Zadeklaruj parametr jako str +# i uzyskaj wsparcie edytora wewnątrz funkcji +def main(user_id: str): + return user_id + + +# Model Pydantic +class User(BaseModel): + id: int + name: str + joined: date +``` + +A one będą mogły zostać później użyte w następujący sposób: + +```Python +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +!!! info + `**second_user_data` oznacza: + + Przekaż klucze i wartości słownika `second_user_data` bezpośrednio jako argumenty klucz-wartość, co jest równoznaczne z: `User(id=4, name="Mary", joined="2018-11-30")` + +### Wsparcie edytora + +Cały framework został zaprojektowany tak, aby był łatwy i intuicyjny w użyciu. Wszystkie pomysły zostały przetestowane na wielu edytorach jeszcze przed rozpoczęciem procesu tworzenia, aby zapewnić najlepsze wrażenia programistyczne. + +Ostatnia ankieta Python developer survey jasno wskazuje, że najczęściej używaną funkcjonalnością jest autouzupełnianie w edytorze. + +Cała struktura frameworku **FastAPI** jest na tym oparta. Autouzupełnianie działa wszędzie. + +Rzadko będziesz musiał wracać do dokumentacji. + +Oto, jak twój edytor może Ci pomóc: + +* Visual Studio Code: + +![wsparcie edytora](https://fastapi.tiangolo.com/img/vscode-completion.png) + +* PyCharm: + +![wsparcie edytora](https://fastapi.tiangolo.com/img/pycharm-completion.png) + +Otrzymasz uzupełnienie nawet w miejscach, w których normalnie uzupełnienia nie ma. Na przykład klucz "price" w treści JSON (który mógł być zagnieżdżony), który pochodzi z zapytania. + +Koniec z wpisywaniem błędnych nazw kluczy, przechodzeniem tam i z powrotem w dokumentacji lub przewijaniem w górę i w dół, aby sprawdzić, czy w końcu użyłeś nazwy `username` czy `user_name`. + +### Zwięzłość + +Wszystko posiada sensowne **domyślne wartości**. Wszędzie znajdziesz opcjonalne konfiguracje. Wszystkie parametry możesz dostroić, aby zrobić to co potrzebujesz do zdefiniowania API. + +Ale domyślnie wszystko **"po prostu działa"**. + +### Walidacja + +* Walidacja większości (lub wszystkich?) **typów danych** Pythona, w tym: + * Obiektów JSON (`dict`). + * Tablic JSON (`list`) ze zdefiniowanym typem elementów. + * Pól tekstowych (`str`) z określeniem minimalnej i maksymalnej długości. + * Liczb (`int`, `float`) z wartościami minimalnymi, maksymalnymi, itp. + +* Walidacja bardziej egzotycznych typów danych, takich jak: + * URL. + * Email. + * UUID. + * ...i inne. + +Cała walidacja jest obsługiwana przez ugruntowaną i solidną bibliotekę **Pydantic**. + +### Bezpieczeństwo i uwierzytelnianie + +Bezpieczeństwo i uwierzytelnianie jest zintegrowane. Bez żadnych kompromisów z bazami czy modelami danych. + +Wszystkie schematy bezpieczeństwa zdefiniowane w OpenAPI, w tym: + +* Podstawowy protokół HTTP. +* **OAuth2** (również z **tokenami JWT**). Sprawdź samouczek [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* Klucze API w: + * Nagłówkach. + * Parametrach zapytań. + * Ciasteczkach, itp. + +Plus wszystkie funkcje bezpieczeństwa Starlette (włączając w to **ciasteczka sesyjne**). + +Wszystko zbudowane jako narzędzia i komponenty wielokrotnego użytku, które można łatwo zintegrować z systemami, magazynami oraz bazami danych - relacyjnymi, NoSQL, itp. + +### Wstrzykiwanie Zależności + +FastAPI zawiera niezwykle łatwy w użyciu, ale niezwykle potężny system Wstrzykiwania Zależności. + +* Nawet zależności mogą mieć zależności, tworząc hierarchię lub **"graf" zależności**. +* Wszystko jest **obsługiwane automatycznie** przez framework. +* Wszystkie zależności mogą wymagać danych w żądaniach oraz rozszerzać ograniczenia i automatyczną dokumentację **operacji na ścieżce**. +* **Automatyczna walidacja** parametrów *operacji na ścieżce* zdefiniowanych w zależnościach. +* Obsługa złożonych systemów uwierzytelniania użytkowników, **połączeń z bazami danych**, itp. +* Bazy danych, front end, itp. **bez kompromisów**, ale wciąż łatwe do integracji. + +### Nieograniczone "wtyczki" + +Lub ujmując to inaczej - brak potrzeby wtyczek. Importuj i używaj kod, który potrzebujesz. + +Każda integracja została zaprojektowana tak, aby była tak prosta w użyciu (z zależnościami), że możesz utworzyć "wtyczkę" dla swojej aplikacji w 2 liniach kodu, używając tej samej struktury i składni, które są używane w *operacjach na ścieżce*. + +### Testy + +* 100% pokrycia kodu testami. +* 100% adnotacji typów. +* Używany w aplikacjach produkcyjnych. + +## Cechy Starlette + +**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) Starlette. Tak więc każdy dodatkowy kod Starlette, który posiadasz, również będzie działał. + +`FastAPI` jest w rzeczywistości podklasą `Starlette`, więc jeśli już znasz lub używasz Starlette, większość funkcji będzie działać w ten sam sposób. + +Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Starlette** (ponieważ FastAPI to po prostu Starlette na sterydach): + +* Bardzo imponująca wydajność. Jest to jeden z najszybszych dostępnych frameworków Pythona, na równi z **NodeJS** i **Go**. +* Wsparcie dla **WebSocket**. +* Zadania w tle. +* Eventy startup i shutdown. +* Klient testowy zbudowany na bazie biblioteki `requests`. +* **CORS**, GZip, pliki statyczne, streamy. +* Obsługa **sesji i ciasteczek**. +* 100% pokrycie testami. +* 100% adnotacji typów. + +## Cechy Pydantic + +**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) Pydantic. Tak więc każdy dodatkowy kod Pydantic, który posiadasz, również będzie działał. + +Wliczając w to zewnętrzne biblioteki, również oparte o Pydantic, takie jak ORM, ODM dla baz danych. + +Oznacza to, że w wielu przypadkach możesz przekazać ten sam obiekt, który otrzymasz z żądania **bezpośrednio do bazy danych**, ponieważ wszystko jest walidowane automatycznie. + +Działa to również w drugą stronę, w wielu przypadkach możesz po prostu przekazać obiekt otrzymany z bazy danych **bezpośrednio do klienta**. + +Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Pydantic** (ponieważ FastAPI bazuje na Pydantic do obsługi wszystkich danych): + +* **Bez prania mózgu**: + * Brak nowego mikrojęzyka do definiowania schematu, którego trzeba się nauczyć. + * Jeśli znasz adnotacje typów Pythona to wiesz jak używać Pydantic. +* Dobrze współpracuje z Twoim **IDE/linterem/mózgiem**: + * Ponieważ struktury danych Pydantic to po prostu instancje klas, które definiujesz; autouzupełnianie, linting, mypy i twoja intuicja powinny działać poprawnie z Twoimi zwalidowanymi danymi. +* **Szybkość**: + * w benchmarkach Pydantic jest szybszy niż wszystkie inne testowane biblioteki. +* Walidacja **złożonych struktur**: + * Wykorzystanie hierarchicznych modeli Pydantic, Pythonowego modułu `typing` zawierającego `List`, `Dict`, itp. + * Walidatory umożliwiają jasne i łatwe definiowanie, sprawdzanie złożonych struktur danych oraz dokumentowanie ich jako JSON Schema. + * Możesz mieć głęboko **zagnieżdżone obiekty JSON** i wszystkie je poddać walidacji i adnotować. +* **Rozszerzalność**: + * Pydantic umożliwia zdefiniowanie niestandardowych typów danych lub rozszerzenie walidacji o metody na modelu, na których użyty jest dekorator walidatora. +* 100% pokrycie testami. diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md index 4a300ae632..bade7a88cb 100644 --- a/docs/pl/docs/index.md +++ b/docs/pl/docs/index.md @@ -106,7 +106,7 @@ Jeżeli tworzysz aplikacje CLI< ## Wymagania -Python 3.6+ +Python 3.7+ FastAPI oparty jest na: @@ -130,7 +130,7 @@ Na serwerze produkcyjnym będziesz także potrzebował serwera ASGI, np. ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -144,7 +144,7 @@ $ pip install uvicorn[standard] * Utwórz plik o nazwie `main.py` z: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -157,7 +157,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -167,7 +167,7 @@ def read_item(item_id: int, q: Optional[str] = None): Jeżeli twój kod korzysta z `async` / `await`, użyj `async def`: ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -180,7 +180,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -258,7 +258,7 @@ Zmodyfikuj teraz plik `main.py`, aby otrzmywał treść (body) żądania `PUT`. Zadeklaruj treść żądania, używając standardowych typów w Pythonie dzięki Pydantic. ```Python hl_lines="4 9-12 25-27" -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -269,7 +269,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -278,7 +278,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -420,7 +420,7 @@ Dla bardziej kompletnych przykładów posiadających więcej funkcjonalności, z * Wiele dodatkowych funkcji (dzięki Starlette) takie jak: * **WebSockety** * **GraphQL** - * bardzo proste testy bazujące na `requests` oraz `pytest` + * bardzo proste testy bazujące na HTTPX oraz `pytest` * **CORS** * **Sesje cookie** * ...i więcej. @@ -435,12 +435,11 @@ Aby dowiedzieć się o tym więcej, zobacz sekcję ujson - dla szybszego "parsowania" danych JSON. * email_validator - dla walidacji adresów email. Używane przez Starlette: -* requests - Wymagane jeżeli chcesz korzystać z `TestClient`. +* httpx - Wymagane jeżeli chcesz korzystać z `TestClient`. * aiofiles - Wymagane jeżeli chcesz korzystać z `FileResponse` albo `StaticFiles`. * jinja2 - Wymagane jeżeli chcesz używać domyślnej konfiguracji szablonów. * python-multipart - Wymagane jeżelich chcesz wsparcie "parsowania" formularzy, używając `request.form()`. diff --git a/docs/pl/docs/tutorial/first-steps.md b/docs/pl/docs/tutorial/first-steps.md new file mode 100644 index 0000000000..9406d703d5 --- /dev/null +++ b/docs/pl/docs/tutorial/first-steps.md @@ -0,0 +1,334 @@ +# Pierwsze kroki + +Najprostszy plik FastAPI może wyglądać tak: + +```Python +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Skopiuj to do pliku `main.py`. + +Uruchom serwer: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +!!! note + Polecenie `uvicorn main:app` odnosi się do: + + * `main`: plik `main.py` ("moduł" Python). + * `app`: obiekt utworzony w pliku `main.py` w lini `app = FastAPI()`. + * `--reload`: sprawia, że serwer uruchamia się ponownie po zmianie kodu. Używany tylko w trakcie tworzenia oprogramowania. + +Na wyjściu znajduje się linia z czymś w rodzaju: + +```hl_lines="4" +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +Ta linia pokazuje adres URL, pod którym Twoja aplikacja jest obsługiwana, na Twoim lokalnym komputerze. + +### Sprawdź to + +Otwórz w swojej przeglądarce http://127.0.0.1:8000. + +Zobaczysz odpowiedź w formacie JSON: + +```JSON +{"message": "Hello World"} +``` + +### Interaktywna dokumentacja API + +Przejdź teraz do http://127.0.0.1:8000/docs. + +Zobaczysz automatyczną i interaktywną dokumentację API (dostarczoną przez Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### Alternatywna dokumentacja API + +Teraz przejdź do http://127.0.0.1:8000/redoc. + +Zobaczysz alternatywną automatycznie wygenerowaną dokumentację API (dostarczoną przez ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +### OpenAPI + +**FastAPI** generuje "schemat" z całym Twoim API przy użyciu standardu **OpenAPI** służącego do definiowania API. + +#### Schema + +"Schema" jest definicją lub opisem czegoś. Nie jest to kod, który go implementuje, ale po prostu abstrakcyjny opis. + +#### API "Schema" + +W typ przypadku, OpenAPI to specyfikacja, która dyktuje sposób definiowania schematu interfejsu API. + +Definicja schematu zawiera ścieżki API, możliwe parametry, które są przyjmowane przez endpointy, itp. + +#### "Schemat" danych + +Termin "schemat" może również odnosić się do wyglądu niektórych danych, takich jak zawartość JSON. + +W takim przypadku będzie to oznaczać atrybuty JSON, ich typy danych itp. + +#### OpenAPI i JSON Schema + +OpenAPI definiuje API Schema dla Twojego API, który zawiera definicje (lub "schematy") danych wysyłanych i odbieranych przez Twój interfejs API przy użyciu **JSON Schema**, standardu dla schematów danych w formacie JSON. + +#### Sprawdź `openapi.json` + +Jeśli jesteś ciekawy, jak wygląda surowy schemat OpenAPI, FastAPI automatycznie generuje JSON Schema z opisami wszystkich Twoich API. + +Możesz to zobaczyć bezpośrednio pod adresem: http://127.0.0.1:8000/openapi.json. + +Zobaczysz JSON zaczynający się od czegoś takiego: + +```JSON +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +#### Do czego służy OpenAPI + +Schemat OpenAPI jest tym, co zasila dwa dołączone interaktywne systemy dokumentacji. + +Istnieją dziesiątki alternatyw, wszystkie oparte na OpenAPI. Możesz łatwo dodać dowolną z nich do swojej aplikacji zbudowanej za pomocą **FastAPI**. + +Możesz go również użyć do automatycznego generowania kodu dla klientów, którzy komunikują się z Twoim API. Na przykład aplikacje frontendowe, mobilne lub IoT. + +## Przypomnijmy, krok po kroku + +### Krok 1: zaimportuj `FastAPI` + +```Python hl_lines="1" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`FastAPI` jest klasą, która zapewnia wszystkie funkcjonalności Twojego API. + +!!! note "Szczegóły techniczne" + `FastAPI` jest klasą, która dziedziczy bezpośrednio z `Starlette`. + + Oznacza to, że możesz korzystać ze wszystkich funkcjonalności Starlette również w `FastAPI`. + + +### Krok 2: utwórz instancję `FastAPI` + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Zmienna `app` będzie tutaj "instancją" klasy `FastAPI`. + +Będzie to główny punkt interakcji przy tworzeniu całego interfejsu API. + +Ta zmienna `app` jest tą samą zmienną, do której odnosi się `uvicorn` w poleceniu: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Jeśli stworzysz swoją aplikację, np.: + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial002.py!} +``` + +I umieścisz to w pliku `main.py`, to będziesz mógł tak wywołać `uvicorn`: + +
+ +```console +$ uvicorn main:my_awesome_api --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Krok 3: wykonaj *operację na ścieżce* + +#### Ścieżka + +"Ścieżka" tutaj odnosi się do ostatniej części adresu URL, zaczynając od pierwszego `/`. + +Więc, w adresie URL takim jak: + +``` +https://example.com/items/foo +``` + +...ścieżką będzie: + +``` +/items/foo +``` + +!!! info + "Ścieżka" jest zazwyczaj nazywana "path", "endpoint" lub "route'. + +Podczas budowania API, "ścieżka" jest głównym sposobem na oddzielenie "odpowiedzialności" i „zasobów”. + +#### Operacje + +"Operacje" tutaj odnoszą się do jednej z "metod" HTTP. + +Jedna z: + +* `POST` +* `GET` +* `PUT` +* `DELETE` + +...i te bardziej egzotyczne: + +* `OPTIONS` +* `HEAD` +* `PATCH` +* `TRACE` + +W protokole HTTP można komunikować się z każdą ścieżką za pomocą jednej (lub więcej) "metod". + +--- + +Podczas tworzenia API zwykle używasz tych metod HTTP do wykonania określonej akcji. + +Zazwyczaj używasz: + +* `POST`: do tworzenia danych. +* `GET`: do odczytywania danych. +* `PUT`: do aktualizacji danych. +* `DELETE`: do usuwania danych. + +Tak więc w OpenAPI każda z metod HTTP nazywana jest "operacją". + +Będziemy je również nazywali "**operacjami**". + +#### Zdefiniuj *dekorator operacji na ścieżce* + +```Python hl_lines="6" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`@app.get("/")` mówi **FastAPI** że funkcja poniżej odpowiada za obsługę żądań, które trafiają do: + +* ścieżki `/` +* używając operacji get + +!!! info "`@decorator` Info" + Składnia `@something` jest w Pythonie nazywana "dekoratorem". + + Umieszczasz to na szczycie funkcji. Jak ładną ozdobną czapkę (chyba stąd wzięła się nazwa). + + "Dekorator" przyjmuje funkcję znajdującą się poniżej jego i coś z nią robi. + + W naszym przypadku dekorator mówi **FastAPI**, że poniższa funkcja odpowiada **ścieżce** `/` z **operacją** `get`. + + Jest to "**dekorator operacji na ścieżce**". + +Możesz również użyć innej operacji: + +* `@app.post()` +* `@app.put()` +* `@app.delete()` + +Oraz tych bardziej egzotycznych: + +* `@app.options()` +* `@app.head()` +* `@app.patch()` +* `@app.trace()` + +!!! tip + Możesz dowolnie używać każdej operacji (metody HTTP). + + **FastAPI** nie narzuca żadnego konkretnego znaczenia. + + Informacje tutaj są przedstawione jako wskazówka, a nie wymóg. + + Na przykład, używając GraphQL, normalnie wykonujesz wszystkie akcje używając tylko operacji `POST`. + +### Krok 4: zdefiniuj **funkcję obsługującą ścieżkę** + +To jest nasza "**funkcja obsługująca ścieżkę**": + +* **ścieżka**: to `/`. +* **operacja**: to `get`. +* **funkcja**: to funkcja poniżej "dekoratora" (poniżej `@app.get("/")`). + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Jest to funkcja Python. + +Zostanie ona wywołana przez **FastAPI** za każdym razem, gdy otrzyma żądanie do adresu URL "`/`" przy użyciu operacji `GET`. + +W tym przypadku jest to funkcja "asynchroniczna". + +--- + +Możesz również zdefiniować to jako normalną funkcję zamiast `async def`: + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial003.py!} +``` + +!!! note + Jeśli nie znasz różnicy, sprawdź [Async: *"In a hurry?"*](/async/#in-a-hurry){.internal-link target=_blank}. + +### Krok 5: zwróć zawartość + +```Python hl_lines="8" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Możesz zwrócić `dict`, `list`, pojedynczą wartość jako `str`, `int`, itp. + +Możesz również zwrócić modele Pydantic (więcej o tym później). + +Istnieje wiele innych obiektów i modeli, które zostaną automatycznie skonwertowane do formatu JSON (w tym ORM itp.). Spróbuj użyć swoich ulubionych, jest bardzo prawdopodobne, że są już obsługiwane. + +## Podsumowanie + +* Zaimportuj `FastAPI`. +* Stwórz instancję `app`. +* Dodaj **dekorator operacji na ścieżce** (taki jak `@app.get("/")`). +* Napisz **funkcję obsługującą ścieżkę** (taką jak `def root(): ...` powyżej). +* Uruchom serwer deweloperski (`uvicorn main:app --reload`). diff --git a/docs/pl/docs/tutorial/index.md b/docs/pl/docs/tutorial/index.md index ed8752a95a..f8c5c60227 100644 --- a/docs/pl/docs/tutorial/index.md +++ b/docs/pl/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Samouczek - Wprowadzenie +# Samouczek Ten samouczek pokaże Ci, krok po kroku, jak używać większości funkcji **FastAPI**. diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml index 0c3d100e77..de18856f44 100644 --- a/docs/pl/mkdocs.yml +++ b/docs/pl/mkdocs.yml @@ -1,139 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/pl/ -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: pl -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- Samouczek: - - tutorial/index.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/pt/docs/advanced/events.md b/docs/pt/docs/advanced/events.md new file mode 100644 index 0000000000..7f6cb6f5d4 --- /dev/null +++ b/docs/pt/docs/advanced/events.md @@ -0,0 +1,163 @@ +# Eventos de vida útil + +Você pode definir a lógica (código) que poderia ser executada antes da aplicação **inicializar**. Isso significa que esse código será executado **uma vez**, **antes** da aplicação **começar a receber requisições**. + +Do mesmo modo, você pode definir a lógica (código) que será executada quando a aplicação estiver sendo **encerrada**. Nesse caso, este código será executado **uma vez**, **depois** de ter possivelmente tratado **várias requisições**. + +Por conta desse código ser executado antes da aplicação **começar** a receber requisições, e logo após **terminar** de lidar com as requisições, ele cobre toda a **vida útil** (_lifespan_) da aplicação (o termo "vida útil" será importante em um segundo 😉). + +Pode ser muito útil para configurar **recursos** que você precisa usar por toda aplicação, e que são **compartilhados** entre as requisições, e/ou que você precisa **limpar** depois. Por exemplo, o pool de uma conexão com o banco de dados ou carregamento de um modelo compartilhado de aprendizado de máquina (_machine learning_). + +## Caso de uso + +Vamos iniciar com um exemplo de **caso de uso** e então ver como resolvê-lo com isso. + +Vamos imaginar que você tem alguns **modelos de _machine learning_** que deseja usar para lidar com as requisições. 🤖 + +Os mesmos modelos são compartilhados entre as requisições, então não é um modelo por requisição, ou um por usuário ou algo parecido. + +Vamos imaginar que o carregamento do modelo pode **demorar bastante tempo**, porque ele tem que ler muitos **dados do disco**. Então você não quer fazer isso a cada requisição. + +Você poderia carregá-lo no nível mais alto do módulo/arquivo, mas isso também poderia significaria **carregar o modelo** mesmo se você estiver executando um simples teste automatizado, então esse teste poderia ser **lento** porque teria que esperar o carregamento do modelo antes de ser capaz de executar uma parte independente do código. + + +Isso é que nós iremos resolver, vamos carregar o modelo antes das requisições serem manuseadas, mas apenas um pouco antes da aplicação começar a receber requisições, não enquanto o código estiver sendo carregado. + +## Vida útil (_Lifespan_) + +Você pode definir essa lógica de *inicialização* e *encerramento* usando os parâmetros de `lifespan` da aplicação `FastAPI`, e um "gerenciador de contexto" (te mostrarei o que é isso a seguir). + +Vamos iniciar com um exemplo e ver isso detalhadamente. + +Nós criamos uma função assíncrona chamada `lifespan()` com `yield` como este: + +```Python hl_lines="16 19" +{!../../../docs_src/events/tutorial003.py!} +``` + +Aqui nós estamos simulando a *inicialização* custosa do carregamento do modelo colocando a (falsa) função de modelo no dicionário com modelos de _machine learning_ antes do `yield`. Este código será executado **antes** da aplicação **começar a receber requisições**, durante a *inicialização*. + +E então, logo após o `yield`, descarregaremos o modelo. Esse código será executado **após** a aplicação **terminar de lidar com as requisições**, pouco antes do *encerramento*. Isso poderia, por exemplo, liberar recursos como memória ou GPU. + +!!! tip "Dica" + O `shutdown` aconteceria quando você estivesse **encerrando** a aplicação. + + Talvez você precise inicializar uma nova versão, ou apenas cansou de executá-la. 🤷 + +### Função _lifespan_ + +A primeira coisa a notar, é que estamos definindo uma função assíncrona com `yield`. Isso é muito semelhante à Dependências com `yield`. + +```Python hl_lines="14-19" +{!../../../docs_src/events/tutorial003.py!} +``` + +A primeira parte da função, antes do `yield`, será executada **antes** da aplicação inicializar. + +E a parte posterior do `yield` irá executar **após** a aplicação ser encerrada. + +### Gerenciador de Contexto Assíncrono + +Se você verificar, a função está decorada com um `@asynccontextmanager`. + +Que converte a função em algo chamado de "**Gerenciador de Contexto Assíncrono**". + +```Python hl_lines="1 13" +{!../../../docs_src/events/tutorial003.py!} +``` + +Um **gerenciador de contexto** em Python é algo que você pode usar em uma declaração `with`, por exemplo, `open()` pode ser usado como um gerenciador de contexto: + +```Python +with open("file.txt") as file: + file.read() +``` + +Nas versões mais recentes de Python, há também um **gerenciador de contexto assíncrono**. Você o usaria com `async with`: + +```Python +async with lifespan(app): + await do_stuff() +``` + +Quando você cria um gerenciador de contexto ou um gerenciador de contexto assíncrono como mencionado acima, o que ele faz é que, antes de entrar no bloco `with`, ele irá executar o código anterior ao `yield`, e depois de sair do bloco `with`, ele irá executar o código depois do `yield`. + +No nosso exemplo de código acima, nós não usamos ele diretamente, mas nós passamos para o FastAPI para ele usá-lo. + +O parâmetro `lifespan` da aplicação `FastAPI` usa um **Gerenciador de Contexto Assíncrono**, então nós podemos passar nosso novo gerenciador de contexto assíncrono do `lifespan` para ele. + +```Python hl_lines="22" +{!../../../docs_src/events/tutorial003.py!} +``` + +## Eventos alternativos (deprecados) + +!!! warning "Aviso" + A maneira recomendada para lidar com a *inicialização* e o *encerramento* é usando o parâmetro `lifespan` da aplicação `FastAPI` como descrito acima. + + Você provavelmente pode pular essa parte. + +Existe uma forma alternativa para definir a execução dessa lógica durante *inicialização* e durante *encerramento*. + +Você pode definir manipuladores de eventos (funções) que precisam ser executadas antes da aplicação inicializar, ou quando a aplicação estiver encerrando. + +Essas funções podem ser declaradas com `async def` ou `def` normal. + +### Evento `startup` + +Para adicionar uma função que deve rodar antes da aplicação iniciar, declare-a com o evento `"startup"`: + +```Python hl_lines="8" +{!../../../docs_src/events/tutorial001.py!} +``` + +Nesse caso, a função de manipulação de evento `startup` irá inicializar os itens do "banco de dados" (só um `dict`) com alguns valores. + +Você pode adicionar mais que uma função de manipulação de evento. + +E sua aplicação não irá começar a receber requisições até que todos os manipuladores de eventos de `startup` sejam concluídos. + +### Evento `shutdown` + +Para adicionar uma função que deve ser executada quando a aplicação estiver encerrando, declare ela com o evento `"shutdown"`: + +```Python hl_lines="6" +{!../../../docs_src/events/tutorial002.py!} +``` + +Aqui, a função de manipulação de evento `shutdown` irá escrever uma linha de texto `"Application shutdown"` no arquivo `log.txt`. + +!!! info "Informação" + Na função `open()`, o `mode="a"` significa "acrescentar", então, a linha irá ser adicionada depois de qualquer coisa que esteja naquele arquivo, sem sobrescrever o conteúdo anterior. + +!!! tip "Dica" + Perceba que nesse caso nós estamos usando a função padrão do Python `open()` que interage com um arquivo. + + Então, isso envolve I/O (input/output), que exige "esperar" que coisas sejam escritas em disco. + + Mas `open()` não usa `async` e `await`. + + Então, nós declaramos uma função de manipulação de evento com o padrão `def` ao invés de `async def`. + +### `startup` e `shutdown` juntos + +Há uma grande chance que a lógica para sua *inicialização* e *encerramento* esteja conectada, você pode querer iniciar alguma coisa e então finalizá-la, adquirir um recurso e então liberá-lo, etc. + +Fazendo isso em funções separadas que não compartilham lógica ou variáveis entre elas é mais difícil já que você precisa armazenar os valores em variáveis globais ou truques parecidos. + +Por causa disso, agora é recomendado em vez disso usar o `lifespan` como explicado acima. + +## Detalhes técnicos + +Só um detalhe técnico para nerds curiosos. 🤓 + +Por baixo, na especificação técnica ASGI, essa é a parte do Protocolo Lifespan, e define eventos chamados `startup` e `shutdown`. + +!!! info "Informação" + Você pode ler mais sobre o manipulador `lifespan` do Starlette na Documentação do Lifespan Starlette. + + Incluindo como manipular estado do lifespan que pode ser usado em outras áreas do seu código. + +## Sub Aplicações + +🚨 Tenha em mente que esses eventos de lifespan (de inicialização e desligamento) irão somente ser executados para a aplicação principal, não para [Sub Aplicações - Montagem](./sub-applications.md){.internal-link target=_blank}. diff --git a/docs/pt/docs/advanced/index.md b/docs/pt/docs/advanced/index.md index d1a57c6d17..7e276f732a 100644 --- a/docs/pt/docs/advanced/index.md +++ b/docs/pt/docs/advanced/index.md @@ -1,4 +1,4 @@ -# Guia de Usuário Avançado - Introdução +# Guia de Usuário Avançado ## Recursos Adicionais diff --git a/docs/pt/docs/contributing.md b/docs/pt/docs/contributing.md index 327b8b6076..02895fcfc8 100644 --- a/docs/pt/docs/contributing.md +++ b/docs/pt/docs/contributing.md @@ -89,61 +89,29 @@ Se ele exibir o binário `pip` em `env/bin/pip` então funcionou. 🎉 !!! tip Toda vez que você instalar um novo pacote com `pip` nesse ambiente, ative o ambiente novamente. - Isso garante que se você usar um programa instalado por aquele pacote (como `flit`), você utilizará aquele de seu ambiente local e não outro que possa estar instalado globalmente. + Isso garante que se você usar um programa instalado por aquele pacote, você utilizará aquele de seu ambiente local e não outro que possa estar instalado globalmente. -### Flit +### pip -**FastAPI** utiliza Flit para construir, empacotar e publicar o projeto. - -Após ativar o ambiente como descrito acima, instale o `flit`: +Após ativar o ambiente como descrito acima:
```console -$ pip install flit +$ pip install -r requirements.txt ---> 100% ```
-Ative novamente o ambiente para ter certeza que você esteja utilizando o `flit` que você acabou de instalar (e não um global). - -E agora use `flit` para instalar as dependências de desenvolvimento: - -=== "Linux, macOS" - -
- - ```console - $ flit install --deps develop --symlink - - ---> 100% - ``` - -
- -=== "Windows" - - Se você está no Windows, use `--pth-file` ao invés de `--symlink`: - -
- - ```console - $ flit install --deps develop --pth-file - - ---> 100% - ``` - -
- Isso irá instalar todas as dependências e seu FastAPI local em seu ambiente local. #### Usando seu FastAPI local Se você cria um arquivo Python que importa e usa FastAPI, e roda com Python de seu ambiente local, ele irá utilizar o código fonte de seu FastAPI local. -E se você atualizar o código fonte do FastAPI local, como ele é instalado com `--symlink` (ou `--pth-file` no Windows), quando você rodar aquele arquivo Python novamente, ele irá utilizar a nova versão do FastAPI que você acabou de editar. +E se você atualizar o código fonte do FastAPI local, como ele é instalado com `-e`, quando você rodar aquele arquivo Python novamente, ele irá utilizar a nova versão do FastAPI que você acabou de editar. Desse modo, você não tem que "instalar" sua versão local para ser capaz de testar cada mudança. @@ -161,7 +129,7 @@ $ bash scripts/format.sh Ele irá organizar também todos os seus imports. -Para que ele organize os imports corretamente, você precisa ter o FastAPI instalado localmente em seu ambiente, com o comando na seção acima usando `--symlink` (ou `--pth-file` no Windows). +Para que ele organize os imports corretamente, você precisa ter o FastAPI instalado localmente em seu ambiente, com o comando na seção acima usando `-e`. ### Formato dos imports diff --git a/docs/pt/docs/deployment.md b/docs/pt/docs/deployment.md index cd820cbd3c..2272467fde 100644 --- a/docs/pt/docs/deployment.md +++ b/docs/pt/docs/deployment.md @@ -336,7 +336,7 @@ Você apenas precisa instalar um servidor ASGI compatível como:
```console - $ pip install uvicorn[standard] + $ pip install "uvicorn[standard]" ---> 100% ``` diff --git a/docs/pt/docs/deployment/docker.md b/docs/pt/docs/deployment/docker.md new file mode 100644 index 0000000000..42c31db29a --- /dev/null +++ b/docs/pt/docs/deployment/docker.md @@ -0,0 +1,701 @@ +# FastAPI em contêineres - Docker + +Ao fazer o deploy de aplicações FastAPI uma abordagem comum é construir uma **imagem de contêiner Linux**. Isso normalmente é feito usando o **Docker**. Você pode a partir disso fazer o deploy dessa imagem de algumas maneiras. + +Usando contêineres Linux você tem diversas vantagens incluindo **segurança**, **replicabilidade**, **simplicidade**, entre outras. + +!!! Dica + Está com pressa e já sabe dessas coisas? Pode ir direto para [`Dockerfile` abaixo 👇](#build-a-docker-image-for-fastapi). + + +
+Visualização do Dockerfile 👀 + +```Dockerfile +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] + +# If running behind a proxy like Nginx or Traefik add --proxy-headers +# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] +``` + +
+ +## O que é um Contêiner + +Contêineres (especificamente contêineres Linux) são um jeito muito **leve** de empacotar aplicações contendo todas as dependências e arquivos necessários enquanto os mantém isolados de outros contêineres (outras aplicações ou componentes) no mesmo sistema. + +Contêineres Linux rodam usando o mesmo kernel Linux do hospedeiro (máquina, máquina virtual, servidor na nuvem, etc). Isso simplesmente significa que eles são muito leves (comparados com máquinas virtuais emulando um sistema operacional completo). + +Dessa forma, contêineres consomem **poucos recursos**, uma quantidade comparável com rodar os processos diretamente (uma máquina virtual consumiria muito mais). + +Contêineres também possuem seus próprios processos (comumente um único processo), sistema de arquivos e rede **isolados** simplificando deploy, segurança, desenvolvimento, etc. + +## O que é uma Imagem de Contêiner + +Um **contêiner** roda a partir de uma **imagem de contêiner**. + +Uma imagem de contêiner é uma versão **estática** de todos os arquivos, variáveis de ambiente e do comando/programa padrão que deve estar presente num contêiner. **Estática** aqui significa que a **imagem** de contêiner não está rodando, não está sendo executada, somente contém os arquivos e metadados empacotados. + +Em contraste com a "**imagem de contêiner**" que contém os conteúdos estáticos armazenados, um "**contêiner**" normalmente se refere à instância rodando, a coisa que está sendo **executada**. + +Quando o **contêiner** é iniciado e está rodando (iniciado a partir de uma **imagem de contêiner**), ele pode criar ou modificar arquivos, variáveis de ambiente, etc. Essas mudanças vão existir somente nesse contêiner, mas não persistirão na imagem subjacente do container (não serão salvas no disco). + +Uma imagem de contêiner é comparável ao arquivo de **programa** e seus conteúdos, ex.: `python` e algum arquivo `main.py`. + +E o **contêiner** em si (em contraste à **imagem de contêiner**) é a própria instância da imagem rodando, comparável a um **processo**. Na verdade, um contêiner está rodando somente quando há um **processo rodando** (e normalmente é somente um processo). O contêiner finaliza quando não há um processo rodando nele. + +## Imagens de contêiner + +Docker tem sido uma das principais ferramentas para criar e gerenciar **imagens de contêiner** e **contêineres**. + +E existe um Docker Hub público com **imagens de contêiner oficiais** pré-prontas para diversas ferramentas, ambientes, bancos de dados e aplicações. + +Por exemplo, há uma Imagem Python oficial. + +E existe muitas outras imagens para diferentes coisas, como bancos de dados, por exemplo: + +* PostgreSQL +* MySQL +* MongoDB +* Redis, etc. + +Usando imagens de contêiner pré-prontas é muito fácil **combinar** e usar diferentes ferramentas. Por exemplo, para testar um novo banco de dados. Em muitos casos, você pode usar as **imagens oficiais** precisando somente de variáveis de ambiente para configurá-las. + +Dessa forma, em muitos casos você pode aprender sobre contêineres e Docker e re-usar essa experiência com diversos componentes e ferramentas. + +Então, você rodaria **vários contêineres** com coisas diferentes, como um banco de dados, uma aplicação Python, um servidor web com uma aplicação frontend React, e conectá-los juntos via sua rede interna. + +Todos os sistemas de gerenciamento de contêineres (como Docker ou Kubernetes) possuem essas funcionalidades de rede integradas a eles. + +## Contêineres e Processos + +Uma **imagem de contêiner** normalmente inclui em seus metadados o programa padrão ou comando que deve ser executado quando o **contêiner** é iniciado e os parâmetros a serem passados para esse programa. Muito similar ao que seria se estivesse na linha de comando. + +Quando um **contêiner** é iniciado, ele irá rodar esse comando/programa (embora você possa sobrescrevê-lo e fazer com que ele rode um comando/programa diferente). + +Um contêiner está rodando enquanto o **processo principal** (comando ou programa) estiver rodando. + +Um contêiner normalmente tem um **único processo**, mas também é possível iniciar sub-processos a partir do processo principal, e dessa forma você terá **vários processos** no mesmo contêiner. + +Mas não é possível ter um contêiner rodando sem **pelo menos um processo rodando**. Se o processo principal parar, o contêiner também para. + +## Construindo uma Imagem Docker para FastAPI + +Okay, vamos construir algo agora! 🚀 + +Eu vou mostrar como construir uma **imagem Docker** para FastAPI **do zero**, baseado na **imagem oficial do Python**. + +Isso é o que você quer fazer na **maioria dos casos**, por exemplo: + +* Usando **Kubernetes** ou ferramentas similares +* Quando rodando em uma **Raspberry Pi** +* Usando um serviço em nuvem que irá rodar uma imagem de contêiner para você, etc. + +### O Pacote Requirements + +Você normalmente teria os **requisitos do pacote** para sua aplicação em algum arquivo. + +Isso pode depender principalmente da ferramenta que você usa para **instalar** esses requisitos. + +O caminho mais comum de fazer isso é ter um arquivo `requirements.txt` com os nomes dos pacotes e suas versões, um por linha. + +Você, naturalmente, usaria as mesmas ideias que você leu em [Sobre Versões do FastAPI](./versions.md){.internal-link target=_blank} para definir os intervalos de versões. + +Por exemplo, seu `requirements.txt` poderia parecer com: + +``` +fastapi>=0.68.0,<0.69.0 +pydantic>=1.8.0,<2.0.0 +uvicorn>=0.15.0,<0.16.0 +``` + +E você normalmente instalaria essas dependências de pacote com `pip`, por exemplo: + +
+ +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic uvicorn +``` + +
+ +!!! info + Há outros formatos e ferramentas para definir e instalar dependências de pacote. + + Eu vou mostrar um exemplo depois usando Poetry em uma seção abaixo. 👇 + +### Criando o Código do **FastAPI** + +* Crie um diretório `app` e entre nele. +* Crie um arquivo vazio `__init__.py`. +* Crie um arquivo `main.py` com: + +```Python +from typing import Optional + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +### Dockerfile + +Agora, no mesmo diretório do projeto, crie um arquivo `Dockerfile` com: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 + +# (2) +WORKDIR /code + +# (3) +COPY ./requirements.txt /code/requirements.txt + +# (4) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5) +COPY ./app /code/app + +# (6) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. Inicie a partir da imagem base oficial do Python. + +2. Defina o diretório de trabalho atual para `/code`. + + Esse é o diretório onde colocaremos o arquivo `requirements.txt` e o diretório `app`. + +3. Copie o arquivo com os requisitos para o diretório `/code`. + + Copie **somente** o arquivo com os requisitos primeiro, não o resto do código. + + Como esse arquivo **não muda com frequência**, o Docker irá detectá-lo e usar o **cache** para esse passo, habilitando o cache para o próximo passo também. + +4. Instale as dependências de pacote vindas do arquivo de requisitos. + + A opção `--no-cache-dir` diz ao `pip` para não salvar os pacotes baixados localmente, pois isso só aconteceria se `pip` fosse executado novamente para instalar os mesmos pacotes, mas esse não é o caso quando trabalhamos com contêineres. + + !!! note + `--no-cache-dir` é apenas relacionado ao `pip`, não tem nada a ver com Docker ou contêineres. + + A opção `--upgrade` diz ao `pip` para atualizar os pacotes se eles já estiverem instalados. + + Por causa do passo anterior de copiar o arquivo, ele pode ser detectado pelo **cache do Docker**, esse passo também **usará o cache do Docker** quando disponível. + + Usando o cache nesse passo irá **salvar** muito **tempo** quando você for construir a imagem repetidas vezes durante o desenvolvimento, ao invés de **baixar e instalar** todas as dependências **toda vez**. + +5. Copie o diretório `./app` dentro do diretório `/code`. + + Como isso tem todo o código contendo o que **muda com mais frequência**, o **cache do Docker** não será usado para esse passo ou para **qualquer passo seguinte** facilmente. + + Então, é importante colocar isso **perto do final** do `Dockerfile`, para otimizar o tempo de construção da imagem do contêiner. + +6. Defina o **comando** para rodar o servidor `uvicorn`. + + `CMD` recebe uma lista de strings, cada uma dessas strings é o que você digitaria na linha de comando separado por espaços. + + Esse comando será executado a partir do **diretório de trabalho atual**, o mesmo diretório `/code` que você definiu acima com `WORKDIR /code`. + + Porque o programa será iniciado em `/code` e dentro dele está o diretório `./app` com seu código, o **Uvicorn** será capaz de ver e **importar** `app` de `app.main`. + +!!! tip + Revise o que cada linha faz clicando em cada bolha com o número no código. 👆 + +Agora você deve ter uma estrutura de diretório como: + +``` +. +├── app +│   ├── __init__.py +│ └── main.py +├── Dockerfile +└── requirements.txt +``` + +#### Por Trás de um Proxy de Terminação TLS + +Se você está executando seu contêiner atrás de um Proxy de Terminação TLS (load balancer) como Nginx ou Traefik, adicione a opção `--proxy-headers`, isso fará com que o Uvicorn confie nos cabeçalhos enviados por esse proxy, informando que o aplicativo está sendo executado atrás do HTTPS, etc. + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +#### Cache Docker + +Existe um truque importante nesse `Dockerfile`, primeiro copiamos o **arquivo com as dependências sozinho**, não o resto do código. Deixe-me te contar o porquê disso. + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +Docker e outras ferramentas **constróem** essas imagens de contêiner **incrementalmente**, adicionando **uma camada em cima da outra**, começando do topo do `Dockerfile` e adicionando qualquer arquivo criado por cada uma das instruções do `Dockerfile`. + +Docker e ferramentas similares também usam um **cache interno** ao construir a imagem, se um arquivo não mudou desde a última vez que a imagem do contêiner foi construída, então ele irá **reutilizar a mesma camada** criada na última vez, ao invés de copiar o arquivo novamente e criar uma nova camada do zero. + +Somente evitar a cópia de arquivos não melhora muito as coisas, mas porque ele usou o cache para esse passo, ele pode **usar o cache para o próximo passo**. Por exemplo, ele pode usar o cache para a instrução que instala as dependências com: + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +O arquivo com os requisitos de pacote **não muda com frequência**. Então, ao copiar apenas esse arquivo, o Docker será capaz de **usar o cache** para esse passo. + +E então, o Docker será capaz de **usar o cache para o próximo passo** que baixa e instala essas dependências. E é aqui que **salvamos muito tempo**. ✨ ...e evitamos tédio esperando. 😪😆 + +Baixar e instalar as dependências do pacote **pode levar minutos**, mas usando o **cache** leva **segundos** no máximo. + +E como você estaria construindo a imagem do contêiner novamente e novamente durante o desenvolvimento para verificar se suas alterações de código estão funcionando, há muito tempo acumulado que isso economizaria. + +A partir daí, perto do final do `Dockerfile`, copiamos todo o código. Como isso é o que **muda com mais frequência**, colocamos perto do final, porque quase sempre, qualquer coisa depois desse passo não será capaz de usar o cache. + +```Dockerfile +COPY ./app /code/app +``` + +### Construindo a Imagem Docker + +Agora que todos os arquivos estão no lugar, vamos construir a imagem do contêiner. + +* Vá para o diretório do projeto (onde está o seu `Dockerfile`, contendo o diretório `app`). +* Construa sua imagem FastAPI: + +
+ +```console +$ docker build -t myimage . + +---> 100% +``` + +
+ +!!! tip + Note o `.` no final, é equivalente a `./`, ele diz ao Docker o diretório a ser usado para construir a imagem do contêiner. + + Nesse caso, é o mesmo diretório atual (`.`). + +### Inicie o contêiner Docker + +* Execute um contêiner baseado na sua imagem: + +
+ +```console +$ docker run -d --name mycontêiner -p 80:80 myimage +``` + +
+ +## Verifique + +Você deve ser capaz de verificar isso no URL do seu contêiner Docker, por exemplo: http://192.168.99.100/items/5?q=somequery ou http://127.0.0.1/items/5?q=somequery (ou equivalente, usando seu host Docker). + +Você verá algo como: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +## Documentação interativa da API + +Agora você pode ir para http://192.168.99.100/docs ou http://127.0.0.1/docs (ou equivalente, usando seu host Docker). + +Você verá a documentação interativa automática da API (fornecida pelo Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +## Documentação alternativa da API + +E você também pode ir para http://192.168.99.100/redoc ou http://127.0.0.1/redoc (ou equivalente, usando seu host Docker). + +Você verá a documentação alternativa automática (fornecida pela ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## Construindo uma Imagem Docker com um Arquivo Único FastAPI + +Se seu FastAPI for um único arquivo, por exemplo, `main.py` sem um diretório `./app`, sua estrutura de arquivos poderia ser assim: + +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` + +Então você só teria que alterar os caminhos correspondentes para copiar o arquivo dentro do `Dockerfile`: + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1) +COPY ./main.py /code/ + +# (2) +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. Copie o arquivo `main.py` para o diretório `/code` diretamente (sem nenhum diretório `./app`). + +2. Execute o Uvicorn e diga a ele para importar o objeto `app` de `main` (em vez de importar de `app.main`). + +Então ajuste o comando Uvicorn para usar o novo módulo `main` em vez de `app.main` para importar o objeto FastAPI `app`. + +## Conceitos de Implantação + +Vamos falar novamente sobre alguns dos mesmos [Conceitos de Implantação](./concepts.md){.internal-link target=_blank} em termos de contêineres. + +Contêineres são principalmente uma ferramenta para simplificar o processo de **construção e implantação** de um aplicativo, mas eles não impõem uma abordagem particular para lidar com esses **conceitos de implantação** e existem várias estratégias possíveis. + +A **boa notícia** é que com cada estratégia diferente há uma maneira de cobrir todos os conceitos de implantação. 🎉 + +Vamos revisar esses **conceitos de implantação** em termos de contêineres: + +* HTTPS +* Executando na inicialização +* Reinicializações +* Replicação (número de processos rodando) +* Memória +* Passos anteriores antes de começar + +## HTTPS + +Se nos concentrarmos apenas na **imagem do contêiner** para um aplicativo FastAPI (e posteriormente no **contêiner** em execução), o HTTPS normalmente seria tratado **externamente** por outra ferramenta. + +Isso poderia ser outro contêiner, por exemplo, com Traefik, lidando com **HTTPS** e aquisição **automática** de **certificados**. + +!!! tip + Traefik tem integrações com Docker, Kubernetes e outros, portanto, é muito fácil configurar e configurar o HTTPS para seus contêineres com ele. + +Alternativamente, o HTTPS poderia ser tratado por um provedor de nuvem como um de seus serviços (enquanto ainda executasse o aplicativo em um contêiner). + +## Executando na inicialização e reinicializações + +Normalmente, outra ferramenta é responsável por **iniciar e executar** seu contêiner. + +Ela poderia ser o **Docker** diretamente, **Docker Compose**, **Kubernetes**, um **serviço de nuvem**, etc. + +Na maioria (ou em todos) os casos, há uma opção simples para habilitar a execução do contêiner na inicialização e habilitar reinicializações em falhas. Por exemplo, no Docker, é a opção de linha de comando `--restart`. + +Sem usar contêineres, fazer aplicativos executarem na inicialização e com reinicializações pode ser trabalhoso e difícil. Mas quando **trabalhando com contêineres** em muitos casos essa funcionalidade é incluída por padrão. ✨ + +## Replicação - Número de Processos + +Se você tiver um cluster de máquinas com **Kubernetes**, Docker Swarm Mode, Nomad ou outro sistema complexo semelhante para gerenciar contêineres distribuídos em várias máquinas, então provavelmente desejará **lidar com a replicação** no **nível do cluster** em vez de usar um **gerenciador de processos** (como o Gunicorn com workers) em cada contêiner. + +Um desses sistemas de gerenciamento de contêineres distribuídos como o Kubernetes normalmente tem alguma maneira integrada de lidar com a **replicação de contêineres** enquanto ainda oferece **balanceamento de carga** para as solicitações recebidas. Tudo no **nível do cluster**. + +Nesses casos, você provavelmente desejará criar uma **imagem do contêiner do zero** como [explicado acima](#dockerfile), instalando suas dependências e executando **um único processo Uvicorn** em vez de executar algo como Gunicorn com trabalhadores Uvicorn. + +### Balanceamento de Carga + +Quando usando contêineres, normalmente você terá algum componente **escutando na porta principal**. Poderia ser outro contêiner que também é um **Proxy de Terminação TLS** para lidar com **HTTPS** ou alguma ferramenta semelhante. + +Como esse componente assumiria a **carga** de solicitações e distribuiria isso entre os trabalhadores de uma maneira (esperançosamente) **balanceada**, ele também é comumente chamado de **Balanceador de Carga**. + +!!! tip + O mesmo componente **Proxy de Terminação TLS** usado para HTTPS provavelmente também seria um **Balanceador de Carga**. + +E quando trabalhar com contêineres, o mesmo sistema que você usa para iniciar e gerenciá-los já terá ferramentas internas para transmitir a **comunicação de rede** (por exemplo, solicitações HTTP) do **balanceador de carga** (que também pode ser um **Proxy de Terminação TLS**) para o(s) contêiner(es) com seu aplicativo. + +### Um Balanceador de Carga - Múltiplos Contêineres de Workers + +Quando trabalhando com **Kubernetes** ou sistemas similares de gerenciamento de contêiner distribuído, usando seus mecanismos de rede internos permitiria que o único **balanceador de carga** que estivesse escutando na **porta principal** transmitisse comunicação (solicitações) para possivelmente **múltiplos contêineres** executando seu aplicativo. + +Cada um desses contêineres executando seu aplicativo normalmente teria **apenas um processo** (ex.: um processo Uvicorn executando seu aplicativo FastAPI). Todos seriam **contêineres idênticos**, executando a mesma coisa, mas cada um com seu próprio processo, memória, etc. Dessa forma, você aproveitaria a **paralelização** em **núcleos diferentes** da CPU, ou até mesmo em **máquinas diferentes**. + +E o sistema de contêiner com o **balanceador de carga** iria **distribuir as solicitações** para cada um dos contêineres com seu aplicativo **em turnos**. Portanto, cada solicitação poderia ser tratada por um dos múltiplos **contêineres replicados** executando seu aplicativo. + +E normalmente esse **balanceador de carga** seria capaz de lidar com solicitações que vão para *outros* aplicativos em seu cluster (por exemplo, para um domínio diferente, ou sob um prefixo de URL diferente), e transmitiria essa comunicação para os contêineres certos para *esse outro* aplicativo em execução em seu cluster. + +### Um Processo por Contêiner + +Nesse tipo de cenário, provavelmente você desejará ter **um único processo (Uvicorn) por contêiner**, pois já estaria lidando com a replicação no nível do cluster. + +Então, nesse caso, você **não** desejará ter um gerenciador de processos como o Gunicorn com trabalhadores Uvicorn, ou o Uvicorn usando seus próprios trabalhadores Uvicorn. Você desejará ter apenas um **único processo Uvicorn** por contêiner (mas provavelmente vários contêineres). + +Tendo outro gerenciador de processos dentro do contêiner (como seria com o Gunicorn ou o Uvicorn gerenciando trabalhadores Uvicorn) só adicionaria **complexidade desnecessária** que você provavelmente já está cuidando com seu sistema de cluster. + +### Contêineres com Múltiplos Processos e Casos Especiais + +Claro, existem **casos especiais** em que você pode querer ter um **contêiner** com um **gerenciador de processos Gunicorn** iniciando vários **processos trabalhadores Uvicorn** dentro. + +Nesses casos, você pode usar a **imagem oficial do Docker** que inclui o **Gunicorn** como um gerenciador de processos executando vários **processos trabalhadores Uvicorn**, e algumas configurações padrão para ajustar o número de trabalhadores com base nos atuais núcleos da CPU automaticamente. Eu vou te contar mais sobre isso abaixo em [Imagem Oficial do Docker com Gunicorn - Uvicorn](#imagem-oficial-do-docker-com-gunicorn-uvicorn). + +Aqui estão alguns exemplos de quando isso pode fazer sentido: + +#### Um Aplicativo Simples + +Você pode querer um gerenciador de processos no contêiner se seu aplicativo for **simples o suficiente** para que você não precise (pelo menos não agora) ajustar muito o número de processos, e você pode simplesmente usar um padrão automatizado (com a imagem oficial do Docker), e você está executando em um **único servidor**, não em um cluster. + +#### Docker Compose + +Você pode estar implantando em um **único servidor** (não em um cluster) com o **Docker Compose**, então você não teria uma maneira fácil de gerenciar a replicação de contêineres (com o Docker Compose) enquanto preserva a rede compartilhada e o **balanceamento de carga**. + +Então você pode querer ter **um único contêiner** com um **gerenciador de processos** iniciando **vários processos trabalhadores** dentro. + +#### Prometheus and Outros Motivos + +Você também pode ter **outros motivos** que tornariam mais fácil ter um **único contêiner** com **múltiplos processos** em vez de ter **múltiplos contêineres** com **um único processo** em cada um deles. + +Por exemplo (dependendo de sua configuração), você poderia ter alguma ferramenta como um exportador do Prometheus no mesmo contêiner que deve ter acesso a **cada uma das solicitações** que chegam. + +Nesse caso, se você tivesse **múltiplos contêineres**, por padrão, quando o Prometheus fosse **ler as métricas**, ele receberia as métricas de **um único contêiner cada vez** (para o contêiner que tratou essa solicitação específica), em vez de receber as **métricas acumuladas** de todos os contêineres replicados. + +Então, nesse caso, poderia ser mais simples ter **um único contêiner** com **múltiplos processos**, e uma ferramenta local (por exemplo, um exportador do Prometheus) no mesmo contêiner coletando métricas do Prometheus para todos os processos internos e expor essas métricas no único contêiner. + +--- + +O ponto principal é que **nenhum** desses são **regras escritas em pedra** que você deve seguir cegamente. Você pode usar essas idéias para **avaliar seu próprio caso de uso** e decidir qual é a melhor abordagem para seu sistema, verificando como gerenciar os conceitos de: + +* Segurança - HTTPS +* Executando na inicialização +* Reinicializações +* Replicação (o número de processos em execução) +* Memória +* Passos anteriores antes de inicializar + +## Memória + +Se você executar **um único processo por contêiner**, terá uma quantidade mais ou menos bem definida, estável e limitada de memória consumida por cada um desses contêineres (mais de um se eles forem replicados). + +E então você pode definir esses mesmos limites e requisitos de memória em suas configurações para seu sistema de gerenciamento de contêineres (por exemplo, no **Kubernetes**). Dessa forma, ele poderá **replicar os contêineres** nas **máquinas disponíveis** levando em consideração a quantidade de memória necessária por eles e a quantidade disponível nas máquinas no cluster. + +Se sua aplicação for **simples**, isso provavelmente **não será um problema**, e você pode não precisar especificar limites de memória rígidos. Mas se você estiver **usando muita memória** (por exemplo, com **modelos de aprendizado de máquina**), deve verificar quanta memória está consumindo e ajustar o **número de contêineres** que executa em **cada máquina** (e talvez adicionar mais máquinas ao seu cluster). + +Se você executar **múltiplos processos por contêiner** (por exemplo, com a imagem oficial do Docker), deve garantir que o número de processos iniciados não **consuma mais memória** do que o disponível. + +## Passos anteriores antes de inicializar e contêineres + +Se você estiver usando contêineres (por exemplo, Docker, Kubernetes), existem duas abordagens principais que você pode usar. + +### Contêineres Múltiplos + +Se você tiver **múltiplos contêineres**, provavelmente cada um executando um **único processo** (por exemplo, em um cluster do **Kubernetes**), então provavelmente você gostaria de ter um **contêiner separado** fazendo o trabalho dos **passos anteriores** em um único contêiner, executando um único processo, **antes** de executar os contêineres trabalhadores replicados. + +!!! info + Se você estiver usando o Kubernetes, provavelmente será um Init Container. + +Se no seu caso de uso não houver problema em executar esses passos anteriores **em paralelo várias vezes** (por exemplo, se você não estiver executando migrações de banco de dados, mas apenas verificando se o banco de dados está pronto), então você também pode colocá-los em cada contêiner logo antes de iniciar o processo principal. + +### Contêiner Único + +Se você tiver uma configuração simples, com um **único contêiner** que então inicia vários **processos trabalhadores** (ou também apenas um processo), então poderia executar esses passos anteriores no mesmo contêiner, logo antes de iniciar o processo com o aplicativo. A imagem oficial do Docker suporta isso internamente. + +## Imagem Oficial do Docker com Gunicorn - Uvicorn + +Há uma imagem oficial do Docker que inclui o Gunicorn executando com trabalhadores Uvicorn, conforme detalhado em um capítulo anterior: [Server Workers - Gunicorn com Uvicorn](./server-workers.md){.internal-link target=_blank}. + +Essa imagem seria útil principalmente nas situações descritas acima em: [Contêineres com Múltiplos Processos e Casos Especiais](#contêineres-com-múltiplos-processos-e-casos-Especiais). + +* tiangolo/uvicorn-gunicorn-fastapi. + +!!! warning + Existe uma grande chance de que você **não** precise dessa imagem base ou de qualquer outra semelhante, e seria melhor construir a imagem do zero, como [descrito acima em: Construa uma Imagem Docker para o FastAPI](#construa-uma-imagem-docker-para-o-fastapi). + +Essa imagem tem um mecanismo de **auto-ajuste** incluído para definir o **número de processos trabalhadores** com base nos núcleos de CPU disponíveis. + +Isso tem **padrões sensíveis**, mas você ainda pode alterar e atualizar todas as configurações com **variáveis de ambiente** ou arquivos de configuração. + +Há também suporte para executar **passos anteriores antes de iniciar** com um script. + +!!! tip + Para ver todas as configurações e opções, vá para a página da imagem Docker: tiangolo/uvicorn-gunicorn-fastapi. + +### Número de Processos na Imagem Oficial do Docker + +O **número de processos** nesta imagem é **calculado automaticamente** a partir dos **núcleos de CPU** disponíveis. + +Isso significa que ele tentará **aproveitar** o máximo de **desempenho** da CPU possível. + +Você também pode ajustá-lo com as configurações usando **variáveis de ambiente**, etc. + +Mas isso também significa que, como o número de processos depende da CPU do contêiner em execução, a **quantidade de memória consumida** também dependerá disso. + +Então, se seu aplicativo consumir muito memória (por exemplo, com modelos de aprendizado de máquina), e seu servidor tiver muitos núcleos de CPU **mas pouca memória**, então seu contêiner pode acabar tentando usar mais memória do que está disponível e degradar o desempenho muito (ou até mesmo travar). 🚨 + +### Criando um `Dockerfile` + +Aqui está como você criaria um `Dockerfile` baseado nessa imagem: + +```Dockerfile +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app +``` + +### Aplicações Maiores + +Se você seguiu a seção sobre a criação de [Aplicações Maiores com Múltiplos Arquivos](../tutorial/bigger-applications.md){.internal-link target=_blank}, seu `Dockerfile` pode parecer com isso: + +```Dockerfile + +```Dockerfile hl_lines="7" +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app/app +``` + +### Quando Usar + +Você provavelmente **não** deve usar essa imagem base oficial (ou qualquer outra semelhante) se estiver usando **Kubernetes** (ou outros) e já estiver definindo **replicação** no nível do cluster, com vários **contêineres**. Nesses casos, é melhor **construir uma imagem do zero** conforme descrito acima: [Construindo uma Imagem Docker para FastAPI](#construindo-uma-imagem-docker-para-fastapi). + +Essa imagem seria útil principalmente nos casos especiais descritos acima em [Contêineres com Múltiplos Processos e Casos Especiais](#contêineres-com-múltiplos-processos-e-casos-Especiais). Por exemplo, se sua aplicação for **simples o suficiente** para que a configuração padrão de número de processos com base na CPU funcione bem, você não quer se preocupar com a configuração manual da replicação no nível do cluster e não está executando mais de um contêiner com seu aplicativo. Ou se você estiver implantando com **Docker Compose**, executando em um único servidor, etc. + +## Deploy da Imagem do Contêiner + +Depois de ter uma imagem de contêiner (Docker), existem várias maneiras de implantá-la. + +Por exemplo: + +* Com **Docker Compose** em um único servidor +* Com um cluster **Kubernetes** +* Com um cluster Docker Swarm Mode +* Com outra ferramenta como o Nomad +* Com um serviço de nuvem que pega sua imagem de contêiner e a implanta + +## Imagem Docker com Poetry + +Se você usa Poetry para gerenciar as dependências do seu projeto, pode usar a construção multi-estágio do Docker: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 as requirements-stage + +# (2) +WORKDIR /tmp + +# (3) +RUN pip install poetry + +# (4) +COPY ./pyproject.toml ./poetry.lock* /tmp/ + +# (5) +RUN poetry export -f requirements.txt --output requirements.txt --without-hashes + +# (6) +FROM python:3.9 + +# (7) +WORKDIR /code + +# (8) +COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt + +# (9) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (10) +COPY ./app /code/app + +# (11) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. Esse é o primeiro estágio, ele é chamado `requirements-stage`. + +2. Defina `/tmp` como o diretório de trabalho atual. + + Aqui é onde geraremos o arquivo `requirements.txt` + +3. Instale o Poetry nesse estágio do Docker. + +4. Copie os arquivos `pyproject.toml` e `poetry.lock` para o diretório `/tmp`. + + Porque está usando `./poetry.lock*` (terminando com um `*`), não irá falhar se esse arquivo ainda não estiver disponível. + +5. Gere o arquivo `requirements.txt`. + +6. Este é o estágio final, tudo aqui será preservado na imagem final do contêiner. + +7. Defina o diretório de trabalho atual como `/code`. + +8. Copie o arquivo `requirements.txt` para o diretório `/code`. + + Essse arquivo só existe no estágio anterior do Docker, é por isso que usamos `--from-requirements-stage` para copiá-lo. + +9. Instale as dependências de pacote do arquivo `requirements.txt` gerado. + +10. Copie o diretório `app` para o diretório `/code`. + +11. Execute o comando `uvicorn`, informando-o para usar o objeto `app` importado de `app.main`. + +!!! tip + Clique nos números das bolhas para ver o que cada linha faz. + +Um **estágio do Docker** é uma parte de um `Dockerfile` que funciona como uma **imagem temporária do contêiner** que só é usada para gerar alguns arquivos para serem usados posteriormente. + +O primeiro estágio será usado apenas para **instalar Poetry** e para **gerar o `requirements.txt`** com as dependências do seu projeto a partir do arquivo `pyproject.toml` do Poetry. + +Esse arquivo `requirements.txt` será usado com `pip` mais tarde no **próximo estágio**. + +Na imagem final do contêiner, **somente o estágio final** é preservado. Os estágios anteriores serão descartados. + +Quando usar Poetry, faz sentido usar **construções multi-estágio do Docker** porque você realmente não precisa ter o Poetry e suas dependências instaladas na imagem final do contêiner, você **apenas precisa** ter o arquivo `requirements.txt` gerado para instalar as dependências do seu projeto. + +Então, no próximo (e último) estágio, você construiria a imagem mais ou menos da mesma maneira descrita anteriormente. + +### Por trás de um proxy de terminação TLS - Poetry + +Novamente, se você estiver executando seu contêiner atrás de um proxy de terminação TLS (balanceador de carga) como Nginx ou Traefik, adicione a opção `--proxy-headers` ao comando: + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +## Recapitulando + +Usando sistemas de contêiner (por exemplo, com **Docker** e **Kubernetes**), torna-se bastante simples lidar com todos os **conceitos de implantação**: + +* HTTPS +* Executando na inicialização +* Reinícios +* Replicação (o número de processos rodando) +* Memória +* Passos anteriores antes de inicializar + +Na maioria dos casos, você provavelmente não desejará usar nenhuma imagem base e, em vez disso, **construir uma imagem de contêiner do zero** baseada na imagem oficial do Docker Python. + +Tendo cuidado com a **ordem** das instruções no `Dockerfile` e o **cache do Docker**, você pode **minimizar os tempos de construção**, para maximizar sua produtividade (e evitar a tédio). 😎 + +Em alguns casos especiais, você pode querer usar a imagem oficial do Docker para o FastAPI. 🤓 diff --git a/docs/pt/docs/deployment/index.md b/docs/pt/docs/deployment/index.md index 1ff0e44a09..6b4290d1d7 100644 --- a/docs/pt/docs/deployment/index.md +++ b/docs/pt/docs/deployment/index.md @@ -1,4 +1,4 @@ -# Implantação - Introdução +# Implantação A implantação de uma aplicação **FastAPI** é relativamente simples. diff --git a/docs/pt/docs/features.md b/docs/pt/docs/features.md index 2b7836a6f2..bd0db8e762 100644 --- a/docs/pt/docs/features.md +++ b/docs/pt/docs/features.md @@ -167,7 +167,7 @@ Com **FastAPI**, você terá todos os recursos do **Starlette** (já que FastAPI * Suporte a **GraphQL**. * Tarefas em processo _background_. * Eventos na inicialização e encerramento. -* Cliente de testes construído sobre `requests`. +* Cliente de testes construído sobre HTTPX. * Respostas em **CORS**, GZip, Static Files, Streaming. * Suporte a **Session e Cookie**. * 100% de cobertura de testes. diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index c1a0dbf0db..591e7f3d4f 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -100,7 +100,7 @@ Se você estiver construindo uma aplicação ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -138,7 +138,7 @@ $ pip install uvicorn[standard] * Crie um arquivo `main.py` com: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -151,7 +151,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -161,7 +161,7 @@ def read_item(item_id: int, q: Optional[str] = None): Se seu código utiliza `async` / `await`, use `async def`: ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -174,7 +174,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -253,6 +253,8 @@ Agora modifique o arquivo `main.py` para receber um corpo para uma requisição Declare o corpo utilizando tipos padrão Python, graças ao Pydantic. ```Python hl_lines="4 9-12 25-27" +from typing import Union + from fastapi import FastAPI from pydantic import BaseModel @@ -262,7 +264,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool] = None @app.get("/") @@ -271,7 +273,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -290,7 +292,7 @@ Agora vá para ujson - para JSON mais rápido "parsing". * email_validator - para validação de email. Usados por Starlette: -* requests - Necessário se você quiser utilizar o `TestClient`. +* httpx - Necessário se você quiser utilizar o `TestClient`. * jinja2 - Necessário se você quiser utilizar a configuração padrão de templates. * python-multipart - Necessário se você quiser suporte com "parsing" de formulário, com `request.form()`. * itsdangerous - Necessário para suporte a `SessionMiddleware`. diff --git a/docs/pt/docs/tutorial/body-multiple-params.md b/docs/pt/docs/tutorial/body-multiple-params.md new file mode 100644 index 0000000000..22f5856a69 --- /dev/null +++ b/docs/pt/docs/tutorial/body-multiple-params.md @@ -0,0 +1,213 @@ +# Corpo - Múltiplos parâmetros + +Agora que nós vimos como usar `Path` e `Query`, veremos usos mais avançados de declarações no corpo da requisição. + +## Misture `Path`, `Query` e parâmetros de corpo + +Primeiro, é claro, você pode misturar `Path`, `Query` e declarações de parâmetro no corpo da requisição livremente e o **FastAPI** saberá o que fazer. + +E você também pode declarar parâmetros de corpo como opcionais, definindo o valor padrão com `None`: + +=== "Python 3.10+" + + ```Python hl_lines="17-19" + {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="19-21" + {!> ../../../docs_src/body_multiple_params/tutorial001.py!} + ``` + +!!! nota + Repare que, neste caso, o `item` que seria capturado a partir do corpo é opcional. Visto que ele possui `None` como valor padrão. + +## Múltiplos parâmetros de corpo + +No exemplo anterior, as *operações de rota* esperariam um JSON no corpo contendo os atributos de um `Item`, exemplo: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +Mas você pode também declarar múltiplos parâmetros de corpo, por exemplo, `item` e `user`: + +=== "Python 3.10+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```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). + +Então, ele usará o nome dos parâmetros como chaves (nome dos campos) no corpo, e espera um corpo como: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + } +} +``` + +!!! nota + Repare que mesmo que o `item` esteja declarado da mesma maneira que antes, agora é esperado que ele esteja dentro do corpo com uma chave `item`. + + +O **FastAPI** fará a conversão automática a partir da requisição, assim esse parâmetro `item` receberá seu respectivo conteúdo e o mesmo ocorrerá com `user`. + +Ele executará a validação dos dados compostos e irá documentá-los de maneira compatível com o esquema OpenAPI e documentação automática. + +## Valores singulares no corpo + +Assim como existem uma `Query` e uma `Path` para definir dados adicionais para parâmetros de consulta e de rota, o **FastAPI** provê o equivalente para `Body`. + +Por exemplo, extendendo o modelo anterior, você poder decidir por ter uma outra chave `importance` no mesmo corpo, além de `item` e `user`. + +Se você declará-lo como é, porque é um valor singular, o **FastAPI** assumirá que se trata de um parâmetro de consulta. + +Mas você pode instruir o **FastAPI** para tratá-lo como outra chave do corpo usando `Body`: + +=== "Python 3.6+" + + ```Python hl_lines="22" + {!> ../../../docs_src/body_multiple_params/tutorial003.py!} + ``` + +=== "Python 3.10+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} + ``` + +Neste caso, o **FastAPI** esperará um corpo como: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + }, + "importance": 5 +} +``` + +Mais uma vez, ele converterá os tipos de dados, validar, documentar, etc. + +## Múltiplos parâmetros de corpo e consulta + +Obviamente, você também pode declarar parâmetros de consulta assim que você precisar, de modo adicional a quaisquer parâmetros de corpo. + +Dado que, por padrão, valores singulares são interpretados como parâmetros de consulta, você não precisa explicitamente adicionar uma `Query`, você pode somente: + +```Python +q: Union[str, None] = None +``` + +Ou como em Python 3.10 e versões superiores: + +```Python +q: str | None = None +``` + +Por exemplo: + +=== "Python 3.10+" + + ```Python hl_lines="26" + {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004.py!} + ``` + +!!! info "Informação" + `Body` também possui todas as validações adicionais e metadados de parâmetros como em `Query`,`Path` e outras que você verá depois. + +## Declare um único parâmetro de corpo indicando sua chave + +Suponha que você tem um único parâmetro de corpo `item`, a partir de um modelo Pydantic `Item`. + +Por padrão, o **FastAPI** esperará que seu conteúdo venha no corpo diretamente. + +Mas se você quiser que ele espere por um JSON com uma chave `item` e dentro dele os conteúdos do modelo, como ocorre ao declarar vários parâmetros de corpo, você pode usar o parâmetro especial de `Body` chamado `embed`: + +```Python +item: Item = Body(embed=True) +``` + +como em: + +=== "Python 3.10+" + + ```Python hl_lines="15" + {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17" + {!> ../../../docs_src/body_multiple_params/tutorial005.py!} + ``` + +Neste caso o **FastAPI** esperará um corpo como: + +```JSON hl_lines="2" +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + } +} +``` + +ao invés de: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +## Recapitulando + +Você pode adicionar múltiplos parâmetros de corpo para sua *função de operação de rota*, mesmo que a requisição possa ter somente um único corpo. + +E o **FastAPI** vai manipulá-los, mandar para você os dados corretos na sua função, e validar e documentar o schema correto na *operação de rota*. + +Você também pode declarar valores singulares para serem recebidos como parte do corpo. + +E você pode instruir o **FastAPI** para requisitar no corpo a indicação de chave mesmo quando existe somente um único parâmetro declarado. diff --git a/docs/pt/docs/tutorial/body-nested-models.md b/docs/pt/docs/tutorial/body-nested-models.md new file mode 100644 index 0000000000..8ab77173e9 --- /dev/null +++ b/docs/pt/docs/tutorial/body-nested-models.md @@ -0,0 +1,248 @@ +# Corpo - Modelos aninhados + +Com o **FastAPI**, você pode definir, validar, documentar e usar modelos profundamente aninhados de forma arbitrária (graças ao Pydantic). + +## Campos do tipo Lista + +Você pode definir um atributo como um subtipo. Por exemplo, uma `list` do Python: + +```Python hl_lines="14" +{!../../../docs_src/body_nested_models/tutorial001.py!} +``` + +Isso fará com que tags seja uma lista de itens mesmo sem declarar o tipo dos elementos desta lista. + +## Campos do tipo Lista com um parâmetro de tipo + +Mas o Python tem uma maneira específica de declarar listas com tipos internos ou "parâmetros de tipo": + +### Importe `List` do typing + +Primeiramente, importe `List` do módulo `typing` que já vem por padrão no Python: + +```Python hl_lines="1" +{!../../../docs_src/body_nested_models/tutorial002.py!} +``` + +### Declare a `List` com um parâmetro de tipo + +Para declarar tipos que têm parâmetros de tipo(tipos internos), como `list`, `dict`, `tuple`: + +* Importe os do modulo `typing` +* Passe o(s) tipo(s) interno(s) como "parâmetros de tipo" usando colchetes: `[` e `]` + +```Python +from typing import List + +my_list: List[str] +``` + +Essa é a sintaxe padrão do Python para declarações de tipo. + +Use a mesma sintaxe padrão para atributos de modelo com tipos internos. + +Portanto, em nosso exemplo, podemos fazer com que `tags` sejam especificamente uma "lista de strings": + + +```Python hl_lines="14" +{!../../../docs_src/body_nested_models/tutorial002.py!} +``` + +## Tipo "set" + + +Mas então, quando nós pensamos mais, percebemos que as tags não devem se repetir, elas provavelmente devem ser strings únicas. + +E que o Python tem um tipo de dados especial para conjuntos de itens únicos, o `set`. + +Então podemos importar `Set` e declarar `tags` como um `set` de `str`s: + + +```Python hl_lines="1 14" +{!../../../docs_src/body_nested_models/tutorial003.py!} +``` + +Com isso, mesmo que você receba uma requisição contendo dados duplicados, ela será convertida em um conjunto de itens exclusivos. + +E sempre que você enviar esses dados como resposta, mesmo se a fonte tiver duplicatas, eles serão gerados como um conjunto de itens exclusivos. + +E também teremos anotações/documentação em conformidade. + +## Modelos aninhados + +Cada atributo de um modelo Pydantic tem um tipo. + +Mas esse tipo pode ser outro modelo Pydantic. + +Portanto, você pode declarar "objects" JSON profundamente aninhados com nomes, tipos e validações de atributos específicos. + +Tudo isso, aninhado arbitrariamente. + +### Defina um sub-modelo + +Por exemplo, nós podemos definir um modelo `Image`: + +```Python hl_lines="9-11" +{!../../../docs_src/body_nested_models/tutorial004.py!} +``` + +### Use o sub-modelo como um tipo + +E então podemos usa-lo como o tipo de um atributo: + +```Python hl_lines="20" +{!../../../docs_src/body_nested_models/tutorial004.py!} +``` + +Isso significa que o **FastAPI** vai esperar um corpo similar à: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +Novamente, apenas fazendo essa declaração, com o **FastAPI**, você ganha: + +* Suporte do editor de texto (compleção, etc), inclusive para modelos aninhados +* Conversão de dados +* Validação de dados +* Documentação automatica + +## Tipos especiais e validação + +Além dos tipos singulares normais como `str`, `int`, `float`, etc. Você também pode usar tipos singulares mais complexos que herdam de `str`. + +Para ver todas as opções possíveis, cheque a documentação para ostipos exoticos do Pydantic. Você verá alguns exemplos no próximo capitulo. + +Por exemplo, no modelo `Image` nós temos um campo `url`, nós podemos declara-lo como um `HttpUrl` do Pydantic invés de como uma `str`: + +```Python hl_lines="4 10" +{!../../../docs_src/body_nested_models/tutorial005.py!} +``` + +A string será verificada para se tornar uma URL válida e documentada no esquema JSON/1OpenAPI como tal. + +## Atributos como listas de submodelos + +Você também pode usar modelos Pydantic como subtipos de `list`, `set`, etc: + +```Python hl_lines="20" +{!../../../docs_src/body_nested_models/tutorial006.py!} +``` + +Isso vai esperar(converter, validar, documentar, etc) um corpo JSON tal qual: + +```JSON hl_lines="11" +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +!!! Informação + Note como o campo `images` agora tem uma lista de objetos de image. + +## Modelos profundamente aninhados + +Você pode definir modelos profundamente aninhados de forma arbitrária: + +```Python hl_lines="9 14 20 23 27" +{!../../../docs_src/body_nested_models/tutorial007.py!} +``` + +!!! Informação + Note como `Offer` tem uma lista de `Item`s, que por sua vez possui opcionalmente uma lista `Image`s + +## Corpos de listas puras + +Se o valor de primeiro nível do corpo JSON que você espera for um `array` do JSON (uma` lista` do Python), você pode declarar o tipo no parâmetro da função, da mesma forma que nos modelos do Pydantic: + + +```Python +images: List[Image] +``` + +como em: + +```Python hl_lines="15" +{!../../../docs_src/body_nested_models/tutorial008.py!} +``` + +## Suporte de editor em todo canto + +E você obtém suporte do editor em todos os lugares. + +Mesmo para itens dentro de listas: + + + +Você não conseguiria este tipo de suporte de editor se estivesse trabalhando diretamente com `dict` em vez de modelos Pydantic. + +Mas você também não precisa se preocupar com eles, os dicts de entrada são convertidos automaticamente e sua saída é convertida automaticamente para JSON também. + +## Corpos de `dict`s arbitrários + +Você também pode declarar um corpo como um `dict` com chaves de algum tipo e valores de outro tipo. + +Sem ter que saber de antemão quais são os nomes de campos/atributos válidos (como seria o caso dos modelos Pydantic). + +Isso seria útil se você deseja receber chaves que ainda não conhece. + +--- + +Outro caso útil é quando você deseja ter chaves de outro tipo, por exemplo, `int`. + +É isso que vamos ver aqui. + +Neste caso, você aceitaria qualquer `dict`, desde que tenha chaves` int` com valores `float`: + +```Python hl_lines="9" +{!../../../docs_src/body_nested_models/tutorial009.py!} +``` + +!!! Dica + Leve em condideração que o JSON só suporta `str` como chaves. + + Mas o Pydantic tem conversão automática de dados. + + Isso significa que, embora os clientes da API só possam enviar strings como chaves, desde que essas strings contenham inteiros puros, o Pydantic irá convertê-los e validá-los. + + E o `dict` que você recebe como `weights` terá, na verdade, chaves `int` e valores` float`. + +## Recapitulação + +Com **FastAPI** você tem a flexibilidade máxima fornecida pelos modelos Pydantic, enquanto seu código é mantido simples, curto e elegante. + +Mas com todos os benefícios: + +* Suporte do editor (compleção em todo canto!) +* Conversão de dados (leia-se parsing/serialização) +* Validação de dados +* Documentação dos esquemas +* Documentação automática diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md index 5abc911779..99e05ab77e 100644 --- a/docs/pt/docs/tutorial/body.md +++ b/docs/pt/docs/tutorial/body.md @@ -158,7 +158,7 @@ Os parâmetros da função serão reconhecidos conforme abaixo: !!! note "Observação" O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`. - O `Optional` em `Optional[str]` não é utilizado pelo FastAPI, mas permite ao seu editor de texto lhe dar um suporte melhor e detectar erros. + O `Union` em `Union[str, None]` não é utilizado pelo FastAPI, mas permite ao seu editor de texto lhe dar um suporte melhor e detectar erros. ## Sem o Pydantic diff --git a/docs/pt/docs/tutorial/encoder.md b/docs/pt/docs/tutorial/encoder.md new file mode 100644 index 0000000000..bb4483fdc8 --- /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/extra-models.md b/docs/pt/docs/tutorial/extra-models.md new file mode 100644 index 0000000000..dd5407eb2d --- /dev/null +++ b/docs/pt/docs/tutorial/extra-models.md @@ -0,0 +1,252 @@ +# Modelos Adicionais + +Continuando com o exemplo anterior, será comum ter mais de um modelo relacionado. + +Isso é especialmente o caso para modelos de usuários, porque: + +* O **modelo de entrada** precisa ser capaz de ter uma senha. +* O **modelo de saída** não deve ter uma senha. +* O **modelo de banco de dados** provavelmente precisaria ter uma senha criptografada. + +!!! danger + Nunca armazene senhas em texto simples dos usuários. Sempre armazene uma "hash segura" que você pode verificar depois. + + Se não souber, você aprenderá o que é uma "senha hash" nos [capítulos de segurança](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. + +## Múltiplos modelos + +Aqui está uma ideia geral de como os modelos poderiam parecer com seus campos de senha e os lugares onde são usados: + +=== "Python 3.6 and above" + + ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" + {!> ../../../docs_src/extra_models/tutorial001.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" + {!> ../../../docs_src/extra_models/tutorial001_py310.py!} + ``` + +### Sobre `**user_in.dict()` + +#### O `.dict()` do Pydantic + +`user_in` é um modelo Pydantic da classe `UserIn`. + +Os modelos Pydantic possuem um método `.dict()` que retorna um `dict` com os dados do modelo. + +Então, se criarmos um objeto Pydantic `user_in` como: + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +e depois chamarmos: + +```Python +user_dict = user_in.dict() +``` + +agora temos um `dict` com os dados na variável `user_dict` (é um `dict` em vez de um objeto de modelo Pydantic). + +E se chamarmos: + +```Python +print(user_dict) +``` + +teríamos um `dict` Python com: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### Desembrulhando um `dict` + +Se tomarmos um `dict` como `user_dict` e passarmos para uma função (ou classe) com `**user_dict`, o Python irá "desembrulhá-lo". Ele passará as chaves e valores do `user_dict` diretamente como argumentos chave-valor. + +Então, continuando com o `user_dict` acima, escrevendo: + +```Python +UserInDB(**user_dict) +``` + +Resultaria em algo equivalente a: + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +Ou mais exatamente, usando `user_dict` diretamente, com qualquer conteúdo que ele possa ter no futuro: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### Um modelo Pydantic a partir do conteúdo de outro + +Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.dict()`, este código: + +```Python +user_dict = user_in.dict() +UserInDB(**user_dict) +``` + +seria equivalente a: + +```Python +UserInDB(**user_in.dict()) +``` + +...porque `user_in.dict()` é um `dict`, e depois fazemos o Python "desembrulhá-lo" passando-o para UserInDB precedido por `**`. + +Então, obtemos um modelo Pydantic a partir dos dados em outro modelo Pydantic. + +#### Desembrulhando um `dict` e palavras-chave extras + +E, então, adicionando o argumento de palavra-chave extra `hashed_password=hashed_password`, como em: + +```Python +UserInDB(**user_in.dict(), hashed_password=hashed_password) +``` + +...acaba sendo como: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +!!! warning + As funções adicionais de suporte são apenas para demonstração de um fluxo possível dos dados, mas é claro que elas não fornecem segurança real. + +## Reduzir duplicação + +Reduzir a duplicação de código é uma das ideias principais no **FastAPI**. + +A duplicação de código aumenta as chances de bugs, problemas de segurança, problemas de desincronização de código (quando você atualiza em um lugar, mas não em outros), etc. + +E esses modelos estão compartilhando muitos dos dados e duplicando nomes e tipos de atributos. + +Nós poderíamos fazer melhor. + +Podemos declarar um modelo `UserBase` que serve como base para nossos outros modelos. E então podemos fazer subclasses desse modelo que herdam seus atributos (declarações de tipo, validação, etc.). + +Toda conversão de dados, validação, documentação, etc. ainda funcionará normalmente. + +Dessa forma, podemos declarar apenas as diferenças entre os modelos (com `password` em texto claro, com `hashed_password` e sem senha): + +=== "Python 3.6 and above" + + ```Python hl_lines="9 15-16 19-20 23-24" + {!> ../../../docs_src/extra_models/tutorial002.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="7 13-14 17-18 21-22" + {!> ../../../docs_src/extra_models/tutorial002_py310.py!} + ``` + +## `Union` ou `anyOf` + +Você pode declarar uma resposta como o `Union` de dois tipos, o que significa que a resposta seria qualquer um dos dois. + +Isso será definido no OpenAPI com `anyOf`. + +Para fazer isso, use a dica de tipo padrão do Python `typing.Union`: + +!!! note + Ao definir um `Union`, inclua o tipo mais específico primeiro, seguido pelo tipo menos específico. No exemplo abaixo, o tipo mais específico `PlaneItem` vem antes de `CarItem` em `Union[PlaneItem, CarItem]`. + +=== "Python 3.6 and above" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003_py310.py!} + ``` + +### `Union` no Python 3.10 + +Neste exemplo, passamos `Union[PlaneItem, CarItem]` como o valor do argumento `response_model`. + +Dado que estamos passando-o como um **valor para um argumento** em vez de colocá-lo em uma **anotação de tipo**, precisamos usar `Union` mesmo no Python 3.10. + +Se estivesse em uma anotação de tipo, poderíamos ter usado a barra vertical, como: + +```Python +some_variable: PlaneItem | CarItem +``` + +Mas se colocarmos isso em `response_model=PlaneItem | CarItem` teríamos um erro, pois o Python tentaria executar uma **operação inválida** entre `PlaneItem` e `CarItem` em vez de interpretar isso como uma anotação de tipo. + +## Lista de modelos + +Da mesma forma, você pode declarar respostas de listas de objetos. + +Para isso, use o padrão Python `typing.List` (ou simplesmente `list` no Python 3.9 e superior): + +=== "Python 3.6 and above" + + ```Python hl_lines="1 20" + {!> ../../../docs_src/extra_models/tutorial004.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="18" + {!> ../../../docs_src/extra_models/tutorial004_py39.py!} + ``` + +## Resposta com `dict` arbitrário + +Você também pode declarar uma resposta usando um simples `dict` arbitrário, declarando apenas o tipo das chaves e valores, sem usar um modelo Pydantic. + +Isso é útil se você não souber os nomes de campo / atributo válidos (que seriam necessários para um modelo Pydantic) antecipadamente. + +Neste caso, você pode usar `typing.Dict` (ou simplesmente dict no Python 3.9 e superior): + +=== "Python 3.6 and above" + + ```Python hl_lines="1 8" + {!> ../../../docs_src/extra_models/tutorial005.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="6" + {!> ../../../docs_src/extra_models/tutorial005_py39.py!} + ``` + +## Em resumo + +Use vários modelos Pydantic e herde livremente para cada caso. + +Não é necessário ter um único modelo de dados por entidade se essa entidade precisar ter diferentes "estados". No caso da "entidade" de usuário com um estado que inclui `password`, `password_hash` e sem senha. diff --git a/docs/pt/docs/tutorial/handling-errors.md b/docs/pt/docs/tutorial/handling-errors.md new file mode 100644 index 0000000000..97a2e3eac9 --- /dev/null +++ b/docs/pt/docs/tutorial/handling-errors.md @@ -0,0 +1,251 @@ +# Manipulação de erros + +Há diversas situações em que você precisa notificar um erro a um cliente que está utilizando a sua API. + +Esse cliente pode ser um browser com um frontend, o código de outra pessoa, um dispositivo IoT, etc. + +Pode ser que você precise comunicar ao cliente que: + +* O cliente não tem direitos para realizar aquela operação. +* O cliente não tem acesso aquele recurso. +* O item que o cliente está tentando acessar não existe. +* etc. + + +Nesses casos, você normalmente retornaria um **HTTP status code** próximo ao status code na faixa do status code **400** (do 400 ao 499). + +Isso é bastante similar ao caso do HTTP status code 200 (do 200 ao 299). Esses "200" status codes significam que, de algum modo, houve sucesso na requisição. + +Os status codes na faixa dos 400 significam que houve um erro por parte do cliente. + +Você se lembra de todos aqueles erros (e piadas) a respeito do "**404 Not Found**"? + +## Use o `HTTPException` + +Para retornar ao cliente *responses* HTTP com erros, use o `HTTPException`. + +### Import `HTTPException` + +```Python hl_lines="1" +{!../../../docs_src/handling_errors/tutorial001.py!} +``` + +### Lance o `HTTPException` no seu código. + +`HTTPException`, ao fundo, nada mais é do que a conjunção entre uma exceção comum do Python e informações adicionais relevantes para APIs. + +E porque é uma exceção do Python, você não **retorna** (return) o `HTTPException`, você lança o (raise) no seu código. + +Isso também significa que, se você está escrevendo uma função de utilidade, a qual você está chamando dentro da sua função de operações de caminhos, e você lança o `HTTPException` dentro da função de utilidade, o resto do seu código não será executado dentro da função de operações de caminhos. Ao contrário, o `HTTPException` irá finalizar a requisição no mesmo instante e enviará o erro HTTP oriundo do `HTTPException` para o cliente. + +O benefício de lançar uma exceção em vez de retornar um valor ficará mais evidente na seção sobre Dependências e Segurança. + +Neste exemplo, quando o cliente pede, na requisição, por um item cujo ID não existe, a exceção com o status code `404` é lançada: + +```Python hl_lines="11" +{!../../../docs_src/handling_errors/tutorial001.py!} +``` + +### A response resultante + + +Se o cliente faz uma requisição para `http://example.com/items/foo` (um `item_id` `"foo"`), esse cliente receberá um HTTP status code 200, e uma resposta JSON: + + +``` +{ + "item": "The Foo Wrestlers" +} +``` + +Mas se o cliente faz uma requisição para `http://example.com/items/bar` (ou seja, um não existente `item_id "bar"`), esse cliente receberá um HTTP status code 404 (o erro "não encontrado" — *not found error*), e uma resposta JSON: + +```JSON +{ + "detail": "Item not found" +} +``` + +!!! tip "Dica" + Quando você lançar um `HTTPException`, você pode passar qualquer valor convertível em JSON como parâmetro de `detail`, e não apenas `str`. + + Você pode passar um `dict` ou um `list`, etc. + Esses tipos de dados são manipulados automaticamente pelo **FastAPI** e convertidos em JSON. + + +## Adicione headers customizados + +Há certas situações em que é bastante útil poder adicionar headers customizados no HTTP error. Exemplo disso seria adicionar headers customizados para tipos de segurança. + +Você provavelmente não precisará utilizar esses headers diretamente no seu código. + +Mas caso você precise, para um cenário mais complexo, você pode adicionar headers customizados: + +```Python hl_lines="14" +{!../../../docs_src/handling_errors/tutorial002.py!} +``` + +## Instalando manipuladores de exceções customizados + +Você pode adicionar manipuladores de exceção customizados com a mesma seção de utilidade de exceções presentes no Starlette + +Digamos que você tenha uma exceção customizada `UnicornException` que você (ou uma biblioteca que você use) precise lançar (`raise`). + +Nesse cenário, se você precisa manipular essa exceção de modo global com o FastAPI, você pode adicionar um manipulador de exceção customizada com `@app.exception_handler()`. + +```Python hl_lines="5-7 13-18 24" +{!../../../docs_src/handling_errors/tutorial003.py!} +``` + +Nesse cenário, se você fizer uma requisição para `/unicorns/yolo`, a *operação de caminho* vai lançar (`raise`) o `UnicornException`. + +Essa exceção será manipulada, contudo, pelo `unicorn_exception_handler`. + +Dessa forma você receberá um erro "limpo", com o HTTP status code `418` e um JSON com o conteúdo: + +```JSON +{"message": "Oops! yolo did something. There goes a rainbow..."} +``` + +!!! note "Detalhes Técnicos" + Você também pode usar `from starlette.requests import Request` and `from starlette.responses import JSONResponse`. + + **FastAPI** disponibiliza o mesmo `starlette.responses` através do `fastapi.responses` por conveniência ao desenvolvedor. Contudo, a maior parte das respostas disponíveis vem diretamente do Starlette. O mesmo acontece com o `Request`. + +## Sobrescreva o manipulador padrão de exceções + +**FastAPI** tem alguns manipuladores padrão de exceções. + +Esses manipuladores são os responsáveis por retornar o JSON padrão de respostas quando você lança (`raise`) o `HTTPException` e quando a requisição tem dados invalidos. + +Você pode sobrescrever esses manipuladores de exceção com os seus próprios manipuladores. + +## Sobrescreva exceções de validação da requisição + +Quando a requisição contém dados inválidos, **FastAPI** internamente lança para o `RequestValidationError`. + +Para sobrescrevê-lo, importe o `RequestValidationError` e use-o com o `@app.exception_handler(RequestValidationError)` para decorar o manipulador de exceções. + +```Python hl_lines="2 14-16" +{!../../../docs_src/handling_errors/tutorial004.py!} +``` + +Se você for ao `/items/foo`, em vez de receber o JSON padrão com o erro: + +```JSON +{ + "detail": [ + { + "loc": [ + "path", + "item_id" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +você receberá a versão em texto: + +``` +1 validation error +path -> item_id + value is not a valid integer (type=type_error.integer) +``` + +### `RequestValidationError` vs `ValidationError` + +!!! warning "Aviso" + Você pode pular estes detalhes técnicos caso eles não sejam importantes para você neste momento. + +`RequestValidationError` é uma subclasse do `ValidationError` existente no Pydantic. + +**FastAPI** faz uso dele para que você veja o erro no seu log, caso você utilize um modelo de Pydantic em `response_model`, e seus dados tenham erro. + +Contudo, o cliente ou usuário não terão acesso a ele. Ao contrário, o cliente receberá um "Internal Server Error" com o HTTP status code `500`. + +E assim deve ser porque seria um bug no seu código ter o `ValidationError` do Pydantic na sua *response*, ou em qualquer outro lugar do seu código (que não na requisição do cliente). + +E enquanto você conserta o bug, os clientes / usuários não deveriam ter acesso às informações internas do erro, porque, desse modo, haveria exposição de uma vulnerabilidade de segurança. + +Do mesmo modo, você pode sobreescrever o `HTTPException`. + +Por exemplo, você pode querer retornar uma *response* em *plain text* ao invés de um JSON para os seguintes erros: + +```Python hl_lines="3-4 9-11 22" +{!../../../docs_src/handling_errors/tutorial004.py!} +``` + +!!! note "Detalhes Técnicos" + Você pode usar `from starlette.responses import PlainTextResponse`. + + **FastAPI** disponibiliza o mesmo `starlette.responses` como `fastapi.responses`, como conveniência a você, desenvolvedor. Contudo, a maior parte das respostas disponíveis vem diretamente do Starlette. + + +### Use o body do `RequestValidationError`. + +O `RequestValidationError` contém o `body` que ele recebeu de dados inválidos. + +Você pode utilizá-lo enquanto desenvolve seu app para conectar o *body* e debugá-lo, e assim retorná-lo ao usuário, etc. + +Tente enviar um item inválido como este: + +```JSON +{ + "title": "towel", + "size": "XL" +} +``` + +Você receberá uma *response* informando-o de que a data é inválida, e contendo o *body* recebido: + +```JSON hl_lines="12-15" +{ + "detail": [ + { + "loc": [ + "body", + "size" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ], + "body": { + "title": "towel", + "size": "XL" + } +} +``` + +#### O `HTTPException` do FastAPI vs o `HTTPException` do Starlette. + +O **FastAPI** tem o seu próprio `HTTPException`. + +E a classe de erro `HTTPException` do **FastAPI** herda da classe de erro do `HTTPException` do Starlette. + +A diferença entre os dois é a de que o `HTTPException` do **FastAPI** permite que você adicione *headers* que serão incluídos nas *responses*. + +Esses *headers* são necessários/utilizados internamente pelo OAuth 2.0 e também por outras utilidades de segurança. + +Portanto, você pode continuar lançando o `HTTPException` do **FastAPI** normalmente no seu código. + +Porém, quando você registrar um manipulador de exceção, você deve registrá-lo através do `HTTPException` do Starlette. + +Dessa forma, se qualquer parte do código interno, extensão ou plug-in do Starlette lançar o `HTTPException`, o seu manipulador de exceção poderá capturar esse lançamento e tratá-lo. + +```Python +from starlette.exceptions import HTTPException as StarletteHTTPException +``` + +### Re-use os manipulares de exceção do **FastAPI** + +Se você quer usar a exceção em conjunto com o mesmo manipulador de exceção *default* do **FastAPI**, você pode importar e re-usar esses manipuladores de exceção do `fastapi.exception_handlers`: + +```Python hl_lines="2-5 15 21" +{!../../../docs_src/handling_errors/tutorial006.py!} +``` + +Nesse exemplo você apenas imprime (`print`) o erro com uma mensagem expressiva. Mesmo assim, dá para pegar a ideia. Você pode usar a exceção e então apenas re-usar o manipulador de exceção *default*. diff --git a/docs/pt/docs/tutorial/header-params.md b/docs/pt/docs/tutorial/header-params.md new file mode 100644 index 0000000000..bc8843327f --- /dev/null +++ b/docs/pt/docs/tutorial/header-params.md @@ -0,0 +1,128 @@ +# Parâmetros de Cabeçalho + +Você pode definir parâmetros de Cabeçalho da mesma maneira que define paramêtros com `Query`, `Path` e `Cookie`. + +## importe `Header` + +Primeiro importe `Header`: + +=== "Python 3.10+" + + ```Python hl_lines="1" + {!> ../../../docs_src/header_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3" + {!> ../../../docs_src/header_params/tutorial001.py!} + ``` + +## Declare parâmetros de `Header` + +Então declare os paramêtros de cabeçalho usando a mesma estrutura que em `Path`, `Query` e `Cookie`. + +O primeiro valor é o valor padrão, você pode passar todas as validações adicionais ou parâmetros de anotação: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/header_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial001.py!} + ``` + +!!! note "Detalhes Técnicos" + `Header` é uma classe "irmã" de `Path`, `Query` e `Cookie`. Ela também herda da mesma classe em comum `Param`. + + Mas lembre-se que quando você importa `Query`, `Path`, `Header`, e outras de `fastapi`, elas são na verdade funções que retornam classes especiais. + +!!! info + Para declarar headers, você precisa usar `Header`, caso contrário, os parâmetros seriam interpretados como parâmetros de consulta. + +## Conversão automática + +`Header` tem algumas funcionalidades a mais em relação a `Path`, `Query` e `Cookie`. + +A maioria dos cabeçalhos padrão são separados pelo caractere "hífen", também conhecido como "sinal de menos" (`-`). + +Mas uma variável como `user-agent` é inválida em Python. + +Portanto, por padrão, `Header` converterá os caracteres de nomes de parâmetros de sublinhado (`_`) para hífen (`-`) para extrair e documentar os cabeçalhos. + +Além disso, os cabeçalhos HTTP não diferenciam maiúsculas de minúsculas, portanto, você pode declará-los com o estilo padrão do Python (também conhecido como "snake_case"). + +Portanto, você pode usar `user_agent` como faria normalmente no código Python, em vez de precisar colocar as primeiras letras em maiúsculas como `User_Agent` ou algo semelhante. + +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.10+" + + ```Python hl_lines="8" + {!> ../../../docs_src/header_params/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/header_params/tutorial002.py!} + ``` + +!!! warning "Aviso" + Antes de definir `convert_underscores` como `False`, lembre-se de que alguns proxies e servidores HTTP não permitem o uso de cabeçalhos com sublinhados. + +## Cabeçalhos duplicados + +É possível receber cabeçalhos duplicados. Isso significa, o mesmo cabeçalho com vários valores. + +Você pode definir esses casos usando uma lista na declaração de tipo. + +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.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/header_params/tutorial003_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```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: + +``` +X-Token: foo +X-Token: bar +``` + +A resposta seria como: + +```JSON +{ + "X-Token values": [ + "bar", + "foo" + ] +} +``` + +## Recapitulando + +Declare cabeçalhos com `Header`, usando o mesmo padrão comum que utiliza-se em `Query`, `Path` e `Cookie`. + +E não se preocupe com sublinhados em suas variáveis, FastAPI cuidará da conversão deles. diff --git a/docs/pt/docs/tutorial/index.md b/docs/pt/docs/tutorial/index.md index f93fd8d75c..5fc0485a07 100644 --- a/docs/pt/docs/tutorial/index.md +++ b/docs/pt/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Tutorial - Guia de Usuário - Introdução +# Tutorial - Guia de Usuário Esse tutorial mostra como usar o **FastAPI** com a maior parte de seus recursos, passo a passo. @@ -43,7 +43,7 @@ Para o tutorial, você deve querer instalá-lo com todas as dependências e recu
```console -$ pip install fastapi[all] +$ pip install "fastapi[all]" ---> 100% ``` @@ -64,7 +64,7 @@ $ pip install fastapi[all] Também instale o `uvicorn` para funcionar como servidor: ``` - pip install uvicorn[standard] + pip install "uvicorn[standard]" ``` E o mesmo para cada dependência opcional que você quiser usar. diff --git a/docs/pt/docs/tutorial/path-operation-configuration.md b/docs/pt/docs/tutorial/path-operation-configuration.md new file mode 100644 index 0000000000..e0a23f6655 --- /dev/null +++ b/docs/pt/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,180 @@ +# Configuração da Operação de Rota + +Existem vários parâmetros que você pode passar para o seu *decorador de operação de rota* para configurá-lo. + +!!! warning "Aviso" + Observe que esses parâmetros são passados diretamente para o *decorador de operação de rota*, não para a sua *função de operação de rota*. + +## Código de Status da Resposta + +Você pode definir o `status_code` (HTTP) para ser usado na resposta da sua *operação de rota*. + +Você pode passar diretamente o código `int`, como `404`. + +Mas se você não se lembrar o que cada código numérico significa, pode usar as constantes de atalho em `status`: + +=== "Python 3.6 and above" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="1 15" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} + ``` + +Esse código de status será usado na resposta e será adicionado ao esquema OpenAPI. + +!!! note "Detalhes Técnicos" + Você também poderia usar `from starlette import status`. + + **FastAPI** fornece o mesmo `starlette.status` como `fastapi.status` apenas como uma conveniência para você, o desenvolvedor. Mas vem diretamente do Starlette. + +## Tags + +Você pode adicionar tags para sua *operação de rota*, passe o parâmetro `tags` com uma `list` de `str` (comumente apenas um `str`): + +=== "Python 3.6 and above" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="15 20 25" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} + ``` + +Eles serão adicionados ao esquema OpenAPI e usados pelas interfaces de documentação automática: + + + +### Tags com Enums + +Se você tem uma grande aplicação, você pode acabar acumulando **várias tags**, e você gostaria de ter certeza de que você sempre usa a **mesma tag** para *operações de rota* relacionadas. + +Nestes casos, pode fazer sentido armazenar as tags em um `Enum`. + +**FastAPI** suporta isso da mesma maneira que com strings simples: + +```Python hl_lines="1 8-10 13 18" +{!../../../docs_src/path_operation_configuration/tutorial002b.py!} +``` + +## Resumo e descrição + +Você pode adicionar um `summary` e uma `description`: + +=== "Python 3.6 and above" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} + ``` + +## Descrição do docstring + +Como as descrições tendem a ser longas e cobrir várias linhas, você pode declarar a descrição da *operação de rota* na docstring da função e o **FastAPI** irá lê-la de lá. + +Você pode escrever Markdown na docstring, ele será interpretado e exibido corretamente (levando em conta a indentação da docstring). + +=== "Python 3.6 and above" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="17-25" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} + ``` + +Ela será usada nas documentações interativas: + + + + +## Descrição da resposta + +Você pode especificar a descrição da resposta com o parâmetro `response_description`: + +=== "Python 3.6 and above" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="19" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} + ``` + +!!! info "Informação" + Note que `response_description` se refere especificamente à resposta, a `description` se refere à *operação de rota* em geral. + +!!! check + OpenAPI especifica que cada *operação de rota* requer uma descrição de resposta. + + Então, se você não fornecer uma, o **FastAPI** irá gerar automaticamente uma de "Resposta bem-sucedida". + + + +## Depreciar uma *operação de rota* + +Se você precisar marcar uma *operação de rota* como descontinuada, mas sem removê-la, passe o parâmetro `deprecated`: + +```Python hl_lines="16" +{!../../../docs_src/path_operation_configuration/tutorial006.py!} +``` + +Ela será claramente marcada como descontinuada nas documentações interativas: + + + +Verifique como *operações de rota* descontinuadas e não descontinuadas se parecem: + + + +## Resumindo + +Você pode configurar e adicionar metadados para suas *operações de rota* facilmente passando parâmetros para os *decoradores de operação de rota*. diff --git a/docs/pt/docs/tutorial/path-params-numeric-validations.md b/docs/pt/docs/tutorial/path-params-numeric-validations.md new file mode 100644 index 0000000000..ec9b74b300 --- /dev/null +++ b/docs/pt/docs/tutorial/path-params-numeric-validations.md @@ -0,0 +1,138 @@ +# Parâmetros da Rota e Validações Numéricas + +Do mesmo modo que você pode declarar mais validações e metadados para parâmetros de consulta com `Query`, você pode declarar os mesmos tipos de validações e metadados para parâmetros de rota com `Path`. + +## Importe `Path` + +Primeiro, importe `Path` de `fastapi`: + +=== "Python 3.10+" + + ```Python hl_lines="1" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + +## Declare metadados + +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.10+" + + ```Python hl_lines="8" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + +!!! note "Nota" + Um parâmetro de rota é sempre obrigatório, como se fizesse parte da rota. + + Então, você deve declará-lo com `...` para marcá-lo como obrigatório. + + Mesmo que você declare-o como `None` ou defina um valor padrão, isso não teria efeito algum, o parâmetro ainda seria obrigatório. + +## Ordene os parâmetros de acordo com sua necessidade + +Suponha que você queira declarar o parâmetro de consulta `q` como uma `str` obrigatória. + +E você não precisa declarar mais nada em relação a este parâmetro, então você não precisa necessariamente usar `Query`. + +Mas você ainda precisa usar `Path` para o parâmetro de rota `item_id`. + +O Python irá acusar se você colocar um elemento com um valor padrão definido antes de outro que não tenha um valor padrão. + +Mas você pode reordená-los, colocando primeiro o elemento sem o valor padrão (o parâmetro de consulta `q`). + +Isso não faz diferença para o **FastAPI**. Ele vai detectar os parâmetros pelos seus nomes, tipos e definições padrão (`Query`, `Path`, etc), sem se importar com a ordem. + +Então, você pode declarar sua função assim: + +```Python hl_lines="7" +{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} +``` + +## Ordene os parâmetros de a acordo com sua necessidade, truques + +Se você quiser declarar o parâmetro de consulta `q` sem um `Query` nem um valor padrão, e o parâmetro de rota `item_id` usando `Path`, e definí-los em uma ordem diferente, Python tem um pequeno truque na sintaxe para isso. + +Passe `*`, como o primeiro parâmetro da função. + +O Python não vai fazer nada com esse `*`, mas ele vai saber que a partir dali os parâmetros seguintes deverão ser chamados argumentos nomeados (pares chave-valor), também conhecidos como kwargs. Mesmo que eles não possuam um valor padrão. + +```Python hl_lines="7" +{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} +``` + +## Validações numéricas: maior que ou igual + +Com `Query` e `Path` (e outras que você verá mais tarde) você pode declarar restrições numéricas. + +Aqui, com `ge=1`, `item_id` precisará ser um número inteiro maior que ("`g`reater than") ou igual ("`e`qual") a 1. + +```Python hl_lines="8" +{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} +``` + +## Validações numéricas: maior que e menor que ou igual + +O mesmo se aplica para: + +* `gt`: maior que (`g`reater `t`han) +* `le`: menor que ou igual (`l`ess than or `e`qual) + +```Python hl_lines="9" +{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} +``` + +## Validações numéricas: valores do tipo float, maior que e menor que + +Validações numéricas também funcionam para valores do tipo `float`. + +Aqui é onde se torna importante a possibilidade de declarar gt e não apenas ge. Com isso você pode especificar, por exemplo, que um valor deve ser maior que `0`, ainda que seja menor que `1`. + +Assim, `0.5` seria um valor válido. Mas `0.0` ou `0` não seria. + +E o mesmo para lt. + +```Python hl_lines="11" +{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} +``` + +## Recapitulando + +Com `Query`, `Path` (e outras que você ainda não viu) você pode declarar metadados e validações de texto do mesmo modo que com [Parâmetros de consulta e validações de texto](query-params-str-validations.md){.internal-link target=_blank}. + +E você também pode declarar validações numéricas: + +* `gt`: maior que (`g`reater `t`han) +* `ge`: maior que ou igual (`g`reater than or `e`qual) +* `lt`: menor que (`l`ess `t`han) +* `le`: menor que ou igual (`l`ess than or `e`qual) + +!!! info "Informação" + `Query`, `Path` e outras classes que você verá a frente são subclasses de uma classe comum `Param`. + + Todas elas compartilham os mesmos parâmetros para validação adicional e metadados que você viu. + +!!! note "Detalhes Técnicos" + Quando você importa `Query`, `Path` e outras de `fastapi`, elas são na verdade funções. + + Que quando chamadas, retornam instâncias de classes de mesmo nome. + + Então, você importa `Query`, que é uma função. E quando você a chama, ela retorna uma instância de uma classe também chamada `Query`. + + Estas funções são assim (ao invés de apenas usar as classes diretamente) para que seu editor não acuse erros sobre seus tipos. + + Dessa maneira você pode user seu editor e ferramentas de desenvolvimento sem precisar adicionar configurações customizadas para ignorar estes erros. diff --git a/docs/pt/docs/tutorial/query-params-str-validations.md b/docs/pt/docs/tutorial/query-params-str-validations.md index baac5f4931..9a9e071db9 100644 --- a/docs/pt/docs/tutorial/query-params-str-validations.md +++ b/docs/pt/docs/tutorial/query-params-str-validations.md @@ -8,12 +8,12 @@ Vamos utilizar essa aplicação como exemplo: {!../../../docs_src/query_params_str_validations/tutorial001.py!} ``` -O parâmetro de consulta `q` é do tipo `Optional[str]`, o que significa que é do tipo `str` mas que também pode ser `None`, e de fato, o valor padrão é `None`, então o FastAPI saberá que não é obrigatório. +O parâmetro de consulta `q` é do tipo `Union[str, None]`, o que significa que é do tipo `str` mas que também pode ser `None`, e de fato, o valor padrão é `None`, então o FastAPI saberá que não é obrigatório. !!! note "Observação" O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`. - O `Optional` em `Optional[str]` não é usado pelo FastAPI, mas permitirá que seu editor lhe dê um melhor suporte e detecte erros. + O `Union` em `Union[str, None]` não é usado pelo FastAPI, mas permitirá que seu editor lhe dê um melhor suporte e detecte erros. ## Validação adicional @@ -35,18 +35,18 @@ Agora utilize-o como valor padrão do seu parâmetro, definindo o parâmetro `ma {!../../../docs_src/query_params_str_validations/tutorial002.py!} ``` -Note que substituímos o valor padrão de `None` para `Query(None)`, o primeiro parâmetro de `Query` serve para o mesmo propósito: definir o valor padrão do parâmetro. +Note que substituímos o valor padrão de `None` para `Query(default=None)`, o primeiro parâmetro de `Query` serve para o mesmo propósito: definir o valor padrão do parâmetro. Então: ```Python -q: Optional[str] = Query(None) +q: Union[str, None] = Query(default=None) ``` ...Torna o parâmetro opcional, da mesma maneira que: ```Python -q: Optional[str] = None +q: Union[str, None] = None ``` Mas o declara explicitamente como um parâmetro de consulta. @@ -61,17 +61,17 @@ Mas o declara explicitamente como um parâmetro de consulta. Ou com: ```Python - = Query(None) + = Query(default=None) ``` E irá utilizar o `None` para detectar que o parâmetro de consulta não é obrigatório. - O `Optional` é apenas para permitir que seu editor de texto lhe dê um melhor suporte. + O `Union` é apenas para permitir que seu editor de texto lhe dê um melhor suporte. Então, podemos passar mais parâmetros para `Query`. Neste caso, o parâmetro `max_length` que se aplica a textos: ```Python -q: str = Query(None, max_length=50) +q: str = Query(default=None, max_length=50) ``` Isso irá validar os dados, mostrar um erro claro quando os dados forem inválidos, e documentar o parâmetro na *operação de rota* do esquema OpenAPI.. @@ -80,7 +80,7 @@ Isso irá validar os dados, mostrar um erro claro quando os dados forem inválid Você também pode incluir um parâmetro `min_length`: -```Python hl_lines="9" +```Python hl_lines="10" {!../../../docs_src/query_params_str_validations/tutorial003.py!} ``` @@ -88,7 +88,7 @@ Você também pode incluir um parâmetro `min_length`: Você pode definir uma expressão regular que combine com um padrão esperado pelo parâmetro: -```Python hl_lines="10" +```Python hl_lines="11" {!../../../docs_src/query_params_str_validations/tutorial004.py!} ``` @@ -126,13 +126,13 @@ q: str em vez desta: ```Python -q: Optional[str] = None +q: Union[str, None] = None ``` Mas agora nós o estamos declarando como `Query`, conforme abaixo: ```Python -q: Optional[str] = Query(None, min_length=3) +q: Union[str, None] = Query(default=None, min_length=3) ``` Então, quando você precisa declarar um parâmetro obrigatório utilizando o `Query`, você pode utilizar `...` como o primeiro argumento: diff --git a/docs/pt/docs/tutorial/query-params.md b/docs/pt/docs/tutorial/query-params.md new file mode 100644 index 0000000000..3ada4fd213 --- /dev/null +++ b/docs/pt/docs/tutorial/query-params.md @@ -0,0 +1,225 @@ +# Parâmetros de Consulta + +Quando você declara outros parâmetros na função que não fazem parte dos parâmetros da rota, esses parâmetros são automaticamente interpretados como parâmetros de "consulta". + +```Python hl_lines="9" +{!../../../docs_src/query_params/tutorial001.py!} +``` + +A consulta é o conjunto de pares chave-valor que vai depois de `?` na URL, separado pelo caractere `&`. + +Por exemplo, na URL: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +...os parâmetros da consulta são: + +* `skip`: com o valor `0` +* `limit`: com o valor `10` + +Como eles são parte da URL, eles são "naturalmente" strings. + +Mas quando você declara eles com os tipos do Python (no exemplo acima, como `int`), eles são convertidos para aquele tipo e validados em relação a ele. + +Todo o processo que era aplicado para parâmetros de rota também é aplicado para parâmetros de consulta: + +* Suporte do editor (obviamente) +* "Parsing" de dados +* Validação de dados +* Documentação automática + +## Valores padrão + +Como os parâmetros de consulta não são uma parte fixa da rota, eles podem ser opcionais e podem ter valores padrão. + +No exemplo acima eles tem valores padrão de `skip=0` e `limit=10`. + +Então, se você for até a URL: + +``` +http://127.0.0.1:8000/items/ +``` + +Seria o mesmo que ir para: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +Mas, se por exemplo você for para: + +``` +http://127.0.0.1:8000/items/?skip=20 +``` + +Os valores dos parâmetros na sua função serão: + +* `skip=20`: Por que você definiu isso na URL +* `limit=10`: Por que esse era o valor padrão + +## Parâmetros opcionais + +Da mesma forma, você pode declarar parâmetros de consulta opcionais, definindo o valor padrão para `None`: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```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. + +!!! check "Verificar" + Você também pode notar que o **FastAPI** é esperto o suficiente para perceber que o parâmetro da rota `item_id` é um parâmetro da rota, e `q` não é, portanto, `q` é o parâmetro de consulta. + + +## Conversão dos tipos de parâmetros de consulta + +Você também pode declarar tipos `bool`, e eles serão convertidos: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params/tutorial003.py!} + ``` + +Nesse caso, se você for para: + +``` +http://127.0.0.1:8000/items/foo?short=1 +``` + +ou + +``` +http://127.0.0.1:8000/items/foo?short=True +``` + +ou + +``` +http://127.0.0.1:8000/items/foo?short=true +``` + +ou + +``` +http://127.0.0.1:8000/items/foo?short=on +``` + +ou + +``` +http://127.0.0.1:8000/items/foo?short=yes +``` + +ou qualquer outra variação (tudo em maiúscula, primeira letra em maiúscula, etc), a sua função vai ver o parâmetro `short` com um valor `bool` de `True`. Caso contrário `False`. + +## Múltiplos parâmetros de rota e consulta + +Você pode declarar múltiplos parâmetros de rota e parâmetros de consulta ao mesmo tempo, o **FastAPI** vai saber o quê é o quê. + +E você não precisa declarar eles em nenhuma ordem específica. + +Eles serão detectados pelo nome: + +=== "Python 3.10+" + + ```Python hl_lines="6 8" + {!> ../../../docs_src/query_params/tutorial004_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8 10" + {!> ../../../docs_src/query_params/tutorial004.py!} + ``` + +## Parâmetros de consulta obrigatórios + +Quando você declara um valor padrão para parâmetros que não são de rota (até agora, nós vimos apenas parâmetros de consulta), então eles não são obrigatórios. + +Caso você não queira adicionar um valor específico mas queira apenas torná-lo opcional, defina o valor padrão como `None`. + +Porém, quando você quiser fazer com que o parâmetro de consulta seja obrigatório, você pode simplesmente não declarar nenhum valor como padrão. + +```Python hl_lines="6-7" +{!../../../docs_src/query_params/tutorial005.py!} +``` + +Aqui o parâmetro de consulta `needy` é um valor obrigatório, do tipo `str`. + +Se você abrir no seu navegador a URL: + +``` +http://127.0.0.1:8000/items/foo-item +``` + +... sem adicionar o parâmetro obrigatório `needy`, você verá um erro como: + +```JSON +{ + "detail": [ + { + "loc": [ + "query", + "needy" + ], + "msg": "field required", + "type": "value_error.missing" + } + ] +} +``` + +Como `needy` é um parâmetro obrigatório, você precisaria defini-lo na URL: + +``` +http://127.0.0.1:8000/items/foo-item?needy=sooooneedy +``` + +...isso deve funcionar: + +```JSON +{ + "item_id": "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.10+" + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params/tutorial006_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params/tutorial006.py!} + ``` + +Nesse caso, existem 3 parâmetros de consulta: + +* `needy`, um `str` obrigatório. +* `skip`, um `int` com o valor padrão `0`. +* `limit`, um `int` opcional. + +!!! tip "Dica" + Você também poderia usar `Enum` da mesma forma que com [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}. diff --git a/docs/pt/docs/tutorial/request-forms-and-files.md b/docs/pt/docs/tutorial/request-forms-and-files.md new file mode 100644 index 0000000000..259f262f44 --- /dev/null +++ b/docs/pt/docs/tutorial/request-forms-and-files.md @@ -0,0 +1,36 @@ +# Formulários e Arquivos da Requisição + +Você pode definir arquivos e campos de formulário ao mesmo tempo usando `File` e `Form`. + +!!! info "Informação" + Para receber arquivos carregados e/ou dados de formulário, primeiro instale `python-multipart`. + + Por exemplo: `pip install python-multipart`. + + +## Importe `File` e `Form` + +```Python hl_lines="1" +{!../../../docs_src/request_forms_and_files/tutorial001.py!} +``` + +## Defina parâmetros de `File` e `Form` + +Crie parâmetros de arquivo e formulário da mesma forma que você faria para `Body` ou `Query`: + +```Python hl_lines="8" +{!../../../docs_src/request_forms_and_files/tutorial001.py!} +``` + +Os arquivos e campos de formulário serão carregados como dados de formulário e você receberá os arquivos e campos de formulário. + +E você pode declarar alguns dos arquivos como `bytes` e alguns como `UploadFile`. + +!!! warning "Aviso" + Você pode declarar vários parâmetros `File` e `Form` em uma *operação de caminho*, mas não é possível declarar campos `Body` para receber como JSON, pois a requisição terá o corpo codificado usando `multipart/form-data` ao invés de `application/json`. + + Isso não é uma limitação do **FastAPI** , é parte do protocolo HTTP. + +## Recapitulando + +Usar `File` e `Form` juntos quando precisar receber dados e arquivos na mesma requisição. diff --git a/docs/pt/docs/tutorial/request-forms.md b/docs/pt/docs/tutorial/request-forms.md new file mode 100644 index 0000000000..b6c1b0e753 --- /dev/null +++ b/docs/pt/docs/tutorial/request-forms.md @@ -0,0 +1,58 @@ +# Dados do formulário + +Quando você precisar receber campos de formulário ao invés de JSON, você pode usar `Form`. + +!!! info "Informação" + Para usar formulários, primeiro instale `python-multipart`. + + Ex: `pip install python-multipart`. + +## Importe `Form` + +Importe `Form` de `fastapi`: + +```Python hl_lines="1" +{!../../../docs_src/request_forms/tutorial001.py!} +``` + +## Declare parâmetros de `Form` + +Crie parâmetros de formulário da mesma forma que você faria para `Body` ou `Query`: + +```Python hl_lines="7" +{!../../../docs_src/request_forms/tutorial001.py!} +``` + +Por exemplo, em uma das maneiras que a especificação OAuth2 pode ser usada (chamada "fluxo de senha"), é necessário enviar um `username` e uma `password` como campos do formulário. + +A spec exige que os campos sejam exatamente nomeados como `username` e `password` e sejam enviados como campos de formulário, não JSON. + +Com `Form` você pode declarar os mesmos metadados e validação que com `Body` (e `Query`, `Path`, `Cookie`). + +!!! info "Informação" + `Form` é uma classe que herda diretamente de `Body`. + +!!! tip "Dica" + Para declarar corpos de formulário, você precisa usar `Form` explicitamente, porque sem ele os parâmetros seriam interpretados como parâmetros de consulta ou parâmetros de corpo (JSON). + +## Sobre "Campos de formulário" + +A forma como os formulários HTML (`
`) enviam os dados para o servidor normalmente usa uma codificação "especial" para esses dados, é diferente do JSON. + +O **FastAPI** fará a leitura desses dados no lugar certo em vez de JSON. + +!!! note "Detalhes técnicos" + Os dados dos formulários são normalmente codificados usando o "tipo de mídia" `application/x-www-form-urlencoded`. + + Mas quando o formulário inclui arquivos, ele é codificado como `multipart/form-data`. Você lerá sobre como lidar com arquivos no próximo capítulo. + + Se você quiser ler mais sobre essas codificações e campos de formulário, vá para o MDN web docs para POST. + +!!! warning "Aviso" + Você pode declarar vários parâmetros `Form` em uma *operação de caminho*, mas não pode declarar campos `Body` que espera receber como JSON, pois a solicitação terá o corpo codificado usando `application/x-www- form-urlencoded` em vez de `application/json`. + + Esta não é uma limitação do **FastAPI**, é parte do protocolo HTTP. + +## Recapitulando + +Use `Form` para declarar os parâmetros de entrada de dados de formulário. diff --git a/docs/pt/docs/tutorial/response-status-code.md b/docs/pt/docs/tutorial/response-status-code.md new file mode 100644 index 0000000000..2df17d4ea1 --- /dev/null +++ b/docs/pt/docs/tutorial/response-status-code.md @@ -0,0 +1,91 @@ +# Código de status de resposta + +Da mesma forma que você pode especificar um modelo de resposta, você também pode declarar o código de status HTTP usado para a resposta com o parâmetro `status_code` em qualquer uma das *operações de caminho*: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* etc. + +```Python hl_lines="6" +{!../../../docs_src/response_status_code/tutorial001.py!} +``` + +!!! note "Nota" + Observe que `status_code` é um parâmetro do método "decorador" (get, post, etc). Não da sua função de *operação de caminho*, como todos os parâmetros e corpo. + +O parâmetro `status_code` recebe um número com o código de status HTTP. + +!!! info "Informação" + `status_code` também pode receber um `IntEnum`, como o do Python `http.HTTPStatus`. + +Dessa forma: + +* Este código de status será retornado na resposta. +* Será documentado como tal no esquema OpenAPI (e, portanto, nas interfaces do usuário): + + + +!!! note "Nota" + Alguns códigos de resposta (consulte a próxima seção) indicam que a resposta não possui um corpo. + + O FastAPI sabe disso e produzirá documentos OpenAPI informando que não há corpo de resposta. + +## Sobre os códigos de status HTTP + +!!! note "Nota" + Se você já sabe o que são códigos de status HTTP, pule para a próxima seção. + +Em HTTP, você envia um código de status numérico de 3 dígitos como parte da resposta. + +Esses códigos de status têm um nome associado para reconhecê-los, mas o importante é o número. + +Resumidamente: + + +* `100` e acima são para "Informações". Você raramente os usa diretamente. As respostas com esses códigos de status não podem ter um corpo. +* **`200`** e acima são para respostas "Bem-sucedidas". Estes são os que você mais usaria. + * `200` é o código de status padrão, o que significa que tudo estava "OK". + * Outro exemplo seria `201`, "Criado". É comumente usado após a criação de um novo registro no banco de dados. + * Um caso especial é `204`, "Sem Conteúdo". Essa resposta é usada quando não há conteúdo para retornar ao cliente e, portanto, a resposta não deve ter um corpo. +* **`300`** e acima são para "Redirecionamento". As respostas com esses códigos de status podem ou não ter um corpo, exceto `304`, "Não modificado", que não deve ter um. +* **`400`** e acima são para respostas de "Erro do cliente". Este é o segundo tipo que você provavelmente mais usaria. + * Um exemplo é `404`, para uma resposta "Não encontrado". + * Para erros genéricos do cliente, você pode usar apenas `400`. +* `500` e acima são para erros do servidor. Você quase nunca os usa diretamente. Quando algo der errado em alguma parte do código do seu aplicativo ou servidor, ele retornará automaticamente um desses códigos de status. + +!!! tip "Dica" + Para saber mais sobre cada código de status e qual código serve para quê, verifique o MDN documentação sobre códigos de status HTTP. + +## Atalho para lembrar os nomes + +Vamos ver o exemplo anterior novamente: + +```Python hl_lines="6" +{!../../../docs_src/response_status_code/tutorial001.py!} +``` + +`201` é o código de status para "Criado". + +Mas você não precisa memorizar o que cada um desses códigos significa. + +Você pode usar as variáveis de conveniência de `fastapi.status`. + +```Python hl_lines="1 6" +{!../../../docs_src/response_status_code/tutorial002.py!} +``` + +Eles são apenas uma conveniência, eles possuem o mesmo número, mas dessa forma você pode usar o autocomplete do editor para encontrá-los: + + + +!!! note "Detalhes técnicos" + Você também pode usar `from starlette import status`. + + **FastAPI** fornece o mesmo `starlette.status` como `fastapi.status` apenas como uma conveniência para você, o desenvolvedor. Mas vem diretamente da Starlette. + + +## Alterando o padrão + +Mais tarde, no [Guia do usuário avançado](../advanced/response-change-status-code.md){.internal-link target=_blank}, você verá como retornar um código de status diferente do padrão que você está declarando aqui. diff --git a/docs/pt/docs/tutorial/security/first-steps.md b/docs/pt/docs/tutorial/security/first-steps.md new file mode 100644 index 0000000000..ed07d1c963 --- /dev/null +++ b/docs/pt/docs/tutorial/security/first-steps.md @@ -0,0 +1,181 @@ +# Segurança - Primeiros Passos + +Vamos imaginar que você tem a sua API **backend** em algum domínio. + +E você tem um **frontend** em outro domínio ou em um path diferente no mesmo domínio (ou em uma aplicação mobile). + +E você quer uma maneira de o frontend autenticar o backend, usando um **username** e **senha**. + +Nós podemos usar o **OAuth2** junto com o **FastAPI**. + +Mas, vamos poupar-lhe o tempo de ler toda a especificação apenas para achar as pequenas informações que você precisa. + +Vamos usar as ferramentas fornecidas pela **FastAPI** para lidar com segurança. + +## Como Parece + +Vamos primeiro usar o código e ver como funciona, e depois voltaremos para entender o que está acontecendo. + +## Crie um `main.py` +Copie o exemplo em um arquivo `main.py`: + +```Python +{!../../../docs_src/security/tutorial001.py!} +``` + +## Execute-o + +!!! informação + Primeiro, instale `python-multipart`. + + Ex: `pip install python-multipart`. + + Isso ocorre porque o **OAuth2** usa "dados de um formulário" para mandar o **username** e **senha**. + +Execute esse exemplo com: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +## Verifique-o + +Vá até a documentação interativa em: http://127.0.0.1:8000/docs. + +Você verá algo deste tipo: + + + +!!! marque o "botão de Autorizar!" + Você já tem um novo "botão de autorizar!". + + E seu *path operation* tem um pequeno cadeado no canto superior direito que você pode clicar. + +E se você clicar, você terá um pequeno formulário de autorização para digitar o `username` e `senha` (e outros campos opcionais): + + + +!!! nota + Não importa o que você digita no formulário, não vai funcionar ainda. Mas nós vamos chegar lá. + +Claro que este não é o frontend para os usuários finais, mas é uma ótima ferramenta automática para documentar interativamente toda sua API. + +Pode ser usado pelo time de frontend (que pode ser você no caso). + +Pode ser usado por aplicações e sistemas third party (de terceiros). + +E também pode ser usada por você mesmo, para debugar, checar e testar a mesma aplicação. + +## O Fluxo da `senha` + +Agora vamos voltar um pouco e entender o que é isso tudo. + +O "fluxo" da `senha` é um dos caminhos ("fluxos") definidos no OAuth2, para lidar com a segurança e autenticação. + +OAuth2 foi projetado para que o backend ou a API pudesse ser independente do servidor que autentica o usuário. + +Mas nesse caso, a mesma aplicação **FastAPI** irá lidar com a API e a autenticação. + +Então, vamos rever de um ponto de vista simplificado: + +* O usuário digita o `username` e a `senha` no frontend e aperta `Enter`. +* O frontend (rodando no browser do usuário) manda o `username` e a `senha` para uma URL específica na sua API (declarada com `tokenUrl="token"`). +* A API checa aquele `username` e `senha`, e responde com um "token" (nós não implementamos nada disso ainda). + * Um "token" é apenas uma string com algum conteúdo que nós podemos utilizar mais tarde para verificar o usuário. + * Normalmente, um token é definido para expirar depois de um tempo. + * Então, o usuário terá que se logar de novo depois de um tempo. + * E se o token for roubado, o risco é menor. Não é como se fosse uma chave permanente que vai funcionar para sempre (na maioria dos casos). + * O frontend armazena aquele token temporariamente em algum lugar. + * O usuário clica no frontend para ir à outra seção daquele frontend do aplicativo web. + * O frontend precisa buscar mais dados daquela API. + * Mas precisa de autenticação para aquele endpoint em específico. + * Então, para autenticar com nossa API, ele manda um header de `Autorização` com o valor `Bearer` mais o token. + * Se o token contém `foobar`, o conteúdo do header de `Autorização` será: `Bearer foobar`. + +## **FastAPI**'s `OAuth2PasswordBearer` + +**FastAPI** fornece várias ferramentas, em diferentes níveis de abstração, para implementar esses recursos de segurança. + +Neste exemplo, nós vamos usar o **OAuth2** com o fluxo de **Senha**, usando um token **Bearer**. Fazemos isso usando a classe `OAuth2PasswordBearer`. + +!!! informação + Um token "bearer" não é a única opção. + + Mas é a melhor no nosso caso. + + E talvez seja a melhor para a maioria dos casos, a não ser que você seja um especialista em OAuth2 e saiba exatamente o porquê de existir outras opções que se adequam melhor às suas necessidades. + + Nesse caso, **FastAPI** também fornece as ferramentas para construir. + +Quando nós criamos uma instância da classe `OAuth2PasswordBearer`, nós passamos pelo parâmetro `tokenUrl` Esse parâmetro contém a URL que o client (o frontend rodando no browser do usuário) vai usar para mandar o `username` e `senha` para obter um token. + +```Python hl_lines="6" +{!../../../docs_src/security/tutorial001.py!} +``` + +!!! dica + Esse `tokenUrl="token"` se refere a uma URL relativa que nós não criamos ainda. Como é uma URL relativa, é equivalente a `./token`. + + Porque estamos usando uma URL relativa, se sua API estava localizada em `https://example.com/`, então irá referir-se à `https://example.com/token`. Mas se sua API estava localizada em `https://example.com/api/v1/`, então irá referir-se à `https://example.com/api/v1/token`. + + Usar uma URL relativa é importante para garantir que sua aplicação continue funcionando, mesmo em um uso avançado tipo [Atrás de um Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}. + +Esse parâmetro não cria um endpoint / *path operation*, mas declara que a URL `/token` vai ser aquela que o client deve usar para obter o token. Essa informação é usada no OpenAPI, e depois na API Interativa de documentação de sistemas. + +Em breve também criaremos o atual path operation. + +!!! informação + Se você é um "Pythonista" muito rigoroso, você pode não gostar do estilo do nome do parâmetro `tokenUrl` em vez de `token_url`. + + Isso ocorre porque está utilizando o mesmo nome que está nas especificações do OpenAPI. Então, se você precisa investigar mais sobre qualquer um desses esquemas de segurança, você pode simplesmente copiar e colar para encontrar mais informações sobre isso. + +A variável `oauth2_scheme` é um instância de `OAuth2PasswordBearer`, mas também é um "callable". + +Pode ser chamada de: + +```Python +oauth2_scheme(some, parameters) +``` + +Então, pode ser usado com `Depends`. + +## Use-o + +Agora você pode passar aquele `oauth2_scheme` em uma dependência com `Depends`. + +```Python hl_lines="10" +{!../../../docs_src/security/tutorial001.py!} +``` + +Esse dependência vai fornecer uma `str` que é atribuído ao parâmetro `token da *função do path operation* + +A **FastAPI** saberá que pode usar essa dependência para definir um "esquema de segurança" no esquema da OpenAPI (e na documentação da API automática). + +!!! informação "Detalhes técnicos" + **FastAPI** saberá que pode usar a classe `OAuth2PasswordBearer` (declarada na dependência) para definir o esquema de segurança na OpenAPI porque herda de `fastapi.security.oauth2.OAuth2`, que por sua vez herda de `fastapi.security.base.Securitybase`. + + Todos os utilitários de segurança que se integram com OpenAPI (e na documentação da API automática) herdam de `SecurityBase`, é assim que **FastAPI** pode saber como integrá-los no OpenAPI. + +## O que ele faz + +Ele irá e olhará na requisição para aquele header de `Autorização`, verificará se o valor é `Bearer` mais algum token, e vai retornar o token como uma `str` + +Se ele não ver o header de `Autorização` ou o valor não tem um token `Bearer`, vai responder com um código de erro 401 (`UNAUTHORIZED`) diretamente. + +Você nem precisa verificar se o token existe para retornar um erro. Você pode ter certeza de que se a sua função for executada, ela terá um `str` nesse token. + +Você já pode experimentar na documentação interativa: + + + +Não estamos verificando a validade do token ainda, mas isso já é um começo + +## Recapitulando + +Então, em apenas 3 ou 4 linhas extras, você já tem alguma forma primitiva de segurança. diff --git a/docs/pt/docs/tutorial/security/index.md b/docs/pt/docs/tutorial/security/index.md index 70f864040a..f94a8ab626 100644 --- a/docs/pt/docs/tutorial/security/index.md +++ b/docs/pt/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# Introdução à segurança +# Segurança Há várias formas de lidar segurança, autenticação e autorização. diff --git a/docs/pt/docs/tutorial/static-files.md b/docs/pt/docs/tutorial/static-files.md new file mode 100644 index 0000000000..009158fc63 --- /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 2bb0b568d4..de18856f44 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -1,162 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/pt/ -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: pt -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- Tutorial - Guia de Usuário: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/body.md - - tutorial/body-fields.md - - tutorial/extra-data-types.md - - tutorial/query-params-str-validations.md - - tutorial/cookie-params.md - - Segurança: - - tutorial/security/index.md - - Guia de Usuário Avançado: - - advanced/index.md -- Implantação: - - deployment/index.md - - deployment/versions.md - - deployment/https.md - - deployment/deta.md -- alternatives.md -- history-design-future.md -- external-links.md -- benchmarks.md -- help-fastapi.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/ru/docs/alternatives.md b/docs/ru/docs/alternatives.md new file mode 100644 index 0000000000..9e3c497d10 --- /dev/null +++ b/docs/ru/docs/alternatives.md @@ -0,0 +1,460 @@ +# Альтернативы, источники вдохновения и сравнения + +Что вдохновило на создание **FastAPI**, сравнение его с альтернативами и чему он научился у них. + +## Введение + +**FastAPI** не существовал бы, если б не было более ранних работ других людей. + +Они создали большое количество инструментов, которые вдохновили меня на создание **FastAPI**. + +Я всячески избегал создания нового фреймворка в течение нескольких лет. +Сначала я пытался собрать все нужные функции, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов. + +Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти функции сразу. +Взять самые лучшие идеи из предыдущих инструментов и, используя новые возможности Python (которых не было до версии 3.6, то есть подсказки типов), объединить их. + +## Предшествующие инструменты + +### Django + +Это самый популярный Python-фреймворк, и он пользуется доверием. +Он используется для создания проектов типа Instagram. + +Django довольно тесно связан с реляционными базами данных (такими как MySQL или PostgreSQL), потому использовать NoSQL базы данных (например, Couchbase, MongoDB, Cassandra и т.п.) в качестве основного хранилища данных - непросто. + +Он был создан для генерации HTML-страниц на сервере, а не для создания API, используемых современными веб-интерфейсами (React, Vue.js, Angular и т.п.) или другими системами (например, IoT) взаимодействующими с сервером. + +### Django REST Framework + +Фреймворк Django REST был создан, как гибкий инструментарий для создания веб-API на основе Django. + +DRF использовался многими компаниями, включая Mozilla, Red Hat и Eventbrite. + +Это был один из первых примеров **автоматического документирования API** и это была одна из первых идей, вдохновивших на создание **FastAPI**. + +!!! note "Заметка" + Django REST Framework был создан Tom Christie. + Он же создал Starlette и Uvicorn, на которых основан **FastAPI**. + +!!! check "Идея для **FastAPI**" + Должно быть автоматическое создание документации API с пользовательским веб-интерфейсом. + +### Flask + +Flask - это "микрофреймворк", в нём нет интеграции с базами данных и многих других вещей, которые предустановлены в Django. + +Его простота и гибкость дают широкие возможности, такие как использование баз данных NoSQL в качестве основной системы хранения данных. + +Он очень прост, его изучение интуитивно понятно, хотя в некоторых местах документация довольно техническая. + +Flask часто используется и для приложений, которым не нужна база данных, настройки прав доступа для пользователей и прочие из множества функций, предварительно встроенных в Django. +Хотя многие из этих функций могут быть добавлены с помощью плагинов. + +Такое разделение на части и то, что это "микрофреймворк", который можно расширить, добавляя необходимые возможности, было ключевой особенностью, которую я хотел сохранить. + +Простота Flask, показалась мне подходящей для создания API. +Но ещё нужно было найти "Django REST Framework" для Flask. + +!!! check "Идеи для **FastAPI**" + Это будет микрофреймворк. К нему легко будет добавить необходимые инструменты и части. + + Должна быть простая и лёгкая в использовании система маршрутизации запросов. + + +### Requests + +На самом деле **FastAPI** не является альтернативой **Requests**. +Их область применения очень разная. + +В принципе, можно использовать Requests *внутри* приложения FastAPI. + +Но всё же я использовал в FastAPI некоторые идеи из Requests. + +**Requests** - это библиотека для взаимодействия с API в качестве клиента, +в то время как **FastAPI** - это библиотека для *создания* API (то есть сервера). + +Они, так или иначе, диаметрально противоположны и дополняют друг друга. + +Requests имеет очень простой и понятный дизайн, очень прост в использовании и имеет разумные значения по умолчанию. +И в то же время он очень мощный и настраиваемый. + +Вот почему на официальном сайте написано: + +> Requests - один из самых загружаемых пакетов Python всех времен + + +Использовать его очень просто. Например, чтобы выполнить запрос `GET`, Вы бы написали: + +```Python +response = requests.get("http://example.com/some/url") +``` + +Противоположная *операция пути* в FastAPI может выглядеть следующим образом: + +```Python hl_lines="1" +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +Глядите, как похоже `requests.get(...)` и `@app.get(...)`. + +!!! check "Идеи для **FastAPI**" + * Должен быть простой и понятный API. + * Нужно использовать названия HTTP-методов (операций) для упрощения понимания происходящего. + * Должны быть разумные настройки по умолчанию и широкие возможности их кастомизации. + + +### Swagger / OpenAPI + +Главной функцией, которую я хотел унаследовать от Django REST Framework, была автоматическая документация API. + +Но потом я обнаружил, что существует стандарт документирования API, использующий JSON (или YAML, расширение JSON) под названием Swagger. + +И к нему уже был создан пользовательский веб-интерфейс. +Таким образом, возможность генерировать документацию Swagger для API позволила бы использовать этот интерфейс. + +В какой-то момент Swagger был передан Linux Foundation и переименован в OpenAPI. + +Вот почему, когда говорят о версии 2.0, обычно говорят "Swagger", а для версии 3+ "OpenAPI". + +!!! check "Идеи для **FastAPI**" + Использовать открытые стандарты для спецификаций API вместо самодельных схем. + + Совместимость с основанными на стандартах пользовательскими интерфейсами: + + * Swagger UI + * ReDoc + + Они были выбраны за популярность и стабильность. + Но сделав беглый поиск, Вы можете найти десятки альтернативных пользовательских интерфейсов для OpenAPI, которые Вы можете использовать с **FastAPI**. + +### REST фреймворки для Flask + +Существует несколько REST фреймворков для Flask, но потратив время и усилия на их изучение, я обнаружил, что многие из них не обновляются или заброшены и имеют нерешённые проблемы из-за которых они непригодны к использованию. + +### Marshmallow + +Одной из основных функций, необходимых системам API, является "сериализация" данных, то есть преобразование данных из кода (Python) во что-то, что может быть отправлено по сети. +Например, превращение объекта содержащего данные из базы данных в объект JSON, конвертация объекта `datetime` в строку и т.п. + +Еще одна важная функция, необходимая API — проверка данных, позволяющая убедиться, что данные действительны и соответствуют заданным параметрам. +Как пример, можно указать, что ожидаются данные типа `int`, а не какая-то произвольная строка. +Это особенно полезно для входящих данных. + +Без системы проверки данных Вам пришлось бы прописывать все проверки вручную. + +Именно для обеспечения этих функций и была создана Marshmallow. +Это отличная библиотека и я много раз пользовался ею раньше. + +Но она была создана до того, как появились подсказки типов Python. +Итак, чтобы определить каждую схему, +Вам нужно использовать определенные утилиты и классы, предоставляемые Marshmallow. + +!!! check "Идея для **FastAPI**" + Использовать код программы для автоматического создания "схем", определяющих типы данных и их проверку. + +### Webargs + +Другая немаловажная функция API - парсинг данных из входящих запросов. + +Webargs - это инструмент, который был создан для этого и поддерживает несколько фреймворков, включая Flask. + +Для проверки данных он использует Marshmallow и создан теми же авторами. + +Это превосходный инструмент и я тоже часто пользовался им до **FastAPI**. + +!!! info "Информация" + Webargs бы создан разработчиками Marshmallow. + +!!! check "Идея для **FastAPI**" + Должна быть автоматическая проверка входных данных. + +### APISpec + +Marshmallow и Webargs осуществляют проверку, анализ и сериализацию данных как плагины. + +Но документации API всё ещё не было. Тогда был создан APISpec. + +Это плагин для множества фреймворков, в том числе и для Starlette. + +Он работает так - Вы записываете определение схем, используя формат YAML, внутри докстринга каждой функции, обрабатывающей маршрут. + +Используя эти докстринги, он генерирует схему OpenAPI. + +Так это работает для Flask, Starlette, Responder и т.п. + +Но теперь у нас возникает новая проблема - наличие постороннего микро-синтаксиса внутри кода Python (большие YAML). + +Редактор кода не особо может помочь в такой парадигме. +А изменив какие-то параметры или схемы для Marshmallow можно забыть отредактировать докстринг с YAML и сгенерированная схема становится недействительной. + +!!! info "Информация" + APISpec тоже был создан авторами Marshmallow. + +!!! check "Идея для **FastAPI**" + Необходима поддержка открытого стандарта для API - OpenAPI. + +### Flask-apispec + +Это плагин для Flask, который связан с Webargs, Marshmallow и APISpec. + +Он получает информацию от Webargs и Marshmallow, а затем использует APISpec для автоматического создания схемы OpenAPI. + +Это отличный, но крайне недооценённый инструмент. +Он должен быть более популярен, чем многие плагины для Flask. +Возможно, это связано с тем, что его документация слишком скудна и абстрактна. + +Он избавил от необходимости писать чужеродный синтаксис YAML внутри докстрингов. + +Такое сочетание Flask, Flask-apispec, Marshmallow и Webargs было моим любимым стеком при построении бэкенда до появления **FastAPI**. + +Использование этого стека привело к созданию нескольких генераторов проектов. Я и некоторые другие команды до сих пор используем их: + +* https://github.com/tiangolo/full-stack +* https://github.com/tiangolo/full-stack-flask-couchbase +* https://github.com/tiangolo/full-stack-flask-couchdb + +Эти генераторы проектов также стали основой для [Генераторов проектов с **FastAPI**](project-generation.md){.internal-link target=_blank}. + +!!! info "Информация" + Как ни странно, но Flask-apispec тоже создан авторами Marshmallow. + +!!! check "Идея для **FastAPI**" + Схема OpenAPI должна создаваться автоматически и использовать тот же код, который осуществляет сериализацию и проверку данных. + +### NestJSAngular) + +Здесь даже не используется Python. NestJS - этот фреймворк написанный на JavaScript (TypeScript), основанный на NodeJS и вдохновлённый Angular. + +Он позволяет получить нечто похожее на то, что можно сделать с помощью Flask-apispec. + +В него встроена система внедрения зависимостей, ещё одна идея взятая от Angular. +Однако требуется предварительная регистрация "внедрений" (как и во всех других известных мне системах внедрения зависимостей), что увеличивает количество и повторяемость кода. + +Так как параметры в нём описываются с помощью типов TypeScript (аналогично подсказкам типов в Python), поддержка редактора работает довольно хорошо. + +Но поскольку данные из TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на подсказки типов для определения проверки данных, сериализации и документации. +Из-за этого и некоторых дизайнерских решений, для валидации, сериализации и автоматической генерации схем, приходится во многих местах добавлять декораторы. +Таким образом, это становится довольно многословным. + +Кроме того, он не очень хорошо справляется с вложенными моделями. +Если в запросе имеется объект JSON, внутренние поля которого, в свою очередь, являются вложенными объектами JSON, это не может быть должным образом задокументировано и проверено. + +!!! check "Идеи для **FastAPI** " + Нужно использовать подсказки типов, чтоб воспользоваться поддержкой редактора кода. + + Нужна мощная система внедрения зависимостей. Необходим способ для уменьшения повторов кода. + +### Sanic + +Sanic был одним из первых чрезвычайно быстрых Python-фреймворков основанных на `asyncio`. +Он был сделан очень похожим на Flask. + +!!! note "Технические детали" + В нём использован `uvloop` вместо стандартного цикла событий `asyncio`, что и сделало его таким быстрым. + + Он явно вдохновил создателей Uvicorn и Starlette, которые в настоящее время быстрее Sanic в открытых бенчмарках. + +!!! check "Идеи для **FastAPI**" + Должна быть сумасшедшая производительность. + + Для этого **FastAPI** основан на Starlette, самом быстром из доступных фреймворков (по замерам незаинтересованных лиц). + +### Falcon + +Falcon - ещё один высокопроизводительный Python-фреймворк. +В нём минимум функций и он создан, чтоб быть основой для других фреймворков, например, Hug. + +Функции в нём получают два параметра - "запрос к серверу" и "ответ сервера". +Затем Вы "читаете" часть запроса и "пишите" часть ответа. +Из-за такой конструкции невозможно объявить параметры запроса и тела сообщения со стандартными подсказками типов Python в качестве параметров функции. + +Таким образом, и валидацию данных, и их сериализацию, и документацию нужно прописывать вручную. +Либо эти функции должны быть встроены во фреймворк, сконструированный поверх Falcon, как в Hug. +Такая же особенность присутствует и в других фреймворках, вдохновлённых идеей Falcon, использовать только один объект запроса и один объект ответа. + +!!! check "Идея для **FastAPI**" + Найдите способы добиться отличной производительности. + + Объявлять параметры `ответа сервера` в функциях, как в Hug. + + Хотя в FastAPI это необязательно и используется в основном для установки заголовков, куки и альтернативных кодов состояния. + +### Molten + +Molten мне попался на начальной стадии написания **FastAPI**. В нём были похожие идеи: + +* Использование подсказок типов. +* Валидация и документация исходя из этих подсказок. +* Система внедрения зависимостей. + +В нём не используются сторонние библиотеки (такие, как Pydantic) для валидации, сериализации и документации. +Поэтому переиспользовать эти определения типов непросто. + +Также требуется более подробная конфигурация и используется стандарт WSGI, который не предназначен для использования с высокопроизводительными инструментами, такими как Uvicorn, Starlette и Sanic, в отличие от ASGI. + +Его система внедрения зависимостей требует предварительной регистрации, и зависимости определяются, как объявления типов. +Из-за этого невозможно объявить более одного "компонента" (зависимости), который предоставляет определенный тип. + +Маршруты объявляются в единственном месте с использованием функций, объявленных в других местах (вместо использования декораторов, в которые могут быть обёрнуты функции, обрабатывающие конкретные ресурсы). +Это больше похоже на Django, чем на Flask и Starlette. +Он разделяет в коде вещи, которые довольно тесно связаны. + +!!! check "Идея для **FastAPI**" + Определить дополнительные проверки типов данных, используя значения атрибутов модели "по умолчанию". + Это улучшает помощь редактора и раньше это не было доступно в Pydantic. + + Фактически это подтолкнуло на обновление Pydantic для поддержки одинакового стиля проверок (теперь этот функционал уже доступен в Pydantic). + +### Hug + +Hug был одним из первых фреймворков, реализовавших объявление параметров API с использованием подсказок типов Python. +Эта отличная идея была использована и другими инструментами. + +При объявлении параметров вместо стандартных типов Python использовались собственные типы, но всё же это был огромный шаг вперед. + +Это также был один из первых фреймворков, генерировавших полную API-схему в формате JSON. + +Данная схема не придерживалась стандартов вроде OpenAPI и JSON Schema. +Поэтому было бы непросто совместить её с другими инструментами, такими как Swagger UI. +Но опять же, это была очень инновационная идея. + +Ещё у него есть интересная и необычная функция: используя один и тот же фреймворк можно создавать и API, и CLI. + +Поскольку он основан на WSGI, старом стандарте для синхронных веб-фреймворков, он не может работать с веб-сокетами и другими модными штуками, но всё равно обладает высокой производительностью. + +!!! info "Информация" + Hug создан Timothy Crosley, автором `isort`, отличного инструмента для автоматической сортировки импортов в Python-файлах. + +!!! check "Идеи для **FastAPI**" + Hug повлиял на создание некоторых частей APIStar и был одним из инструментов, которые я счел наиболее многообещающими, наряду с APIStar. + + Hug натолкнул на мысли использовать в **FastAPI** подсказки типов Python для автоматического создания схемы, определяющей API и его параметры. + + Hug вдохновил **FastAPI** объявить параметр `ответа` в функциях для установки заголовков и куки. + +### APIStar (<= 0.5) + +Непосредственно перед тем, как принять решение о создании **FastAPI**, я обнаружил **APIStar**. +В нем было почти все, что я искал и у него был отличный дизайн. + +Это была одна из первых реализаций фреймворка, использующего подсказки типов для объявления параметров и запросов, которые я когда-либо видел (до NestJS и Molten). +Я нашёл его примерно в то же время, что и Hug, но APIStar использовал стандарт OpenAPI. + +В нём были автоматические проверка и сериализация данных и генерация схемы OpenAPI основанные на подсказках типов в нескольких местах. + +При определении схемы тела сообщения не использовались подсказки типов, как в Pydantic, это больше похоже на Marshmallow, поэтому помощь редактора была недостаточно хорошей, но всё же APIStar был лучшим доступным вариантом. + +На тот момент у него были лучшие показатели производительности (проигрывающие только Starlette). + +Изначально у него не было автоматической документации API для веб-интерфейса, но я знал, что могу добавить к нему Swagger UI. + +В APIStar была система внедрения зависимостей, которая тоже требовала предварительную регистрацию компонентов, как и ранее описанные инструменты. +Но, тем не менее, это была отличная штука. + +Я не смог использовать его в полноценном проекте, так как были проблемы со встраиванием функций безопасности в схему OpenAPI, из-за которых невозможно было встроить все функции, применяемые в генераторах проектов на основе Flask-apispec. +Я добавил в свой список задач создание пул-реквеста, добавляющего эту функциональность. + +В дальнейшем фокус проекта сместился. + +Это больше не был API-фреймворк, так как автор сосредоточился на Starlette. + +Ныне APIStar - это набор инструментов для проверки спецификаций OpenAPI. + +!!! info "Информация" + APIStar был создан Tom Christie. Тот самый парень, который создал: + + * Django REST Framework + * Starlette (на котором основан **FastAPI**) + * Uvicorn (используемый в Starlette и **FastAPI**) + +!!! check "Идеи для **FastAPI**" + Воплощение. + + Мне казалось блестящей идеей объявлять множество функций (проверка данных, сериализация, документация) с помощью одних и тех же типов Python, которые при этом обеспечивают ещё и помощь редактора кода. + + После долгих поисков среди похожих друг на друга фреймворков и сравнения их различий, APIStar стал самым лучшим выбором. + + Но APIStar перестал быть фреймворком для создания веб-сервера, зато появился Starlette, новая и лучшая основа для построения подобных систем. + Это была последняя капля, сподвигнувшая на создание **FastAPI**. + + Я считаю **FastAPI** "духовным преемником" APIStar, улучившим его возможности благодаря урокам, извлечённым из всех упомянутых выше инструментов. + +## Что используется в **FastAPI** + +### Pydantic + +Pydantic - это библиотека для валидации данных, сериализации и документирования (используя JSON Schema), основываясь на подсказках типов Python, что делает его чрезвычайно интуитивным. + +Его можно сравнить с Marshmallow, хотя в бенчмарках Pydantic быстрее, чем Marshmallow. +И он основан на тех же подсказках типов, которые отлично поддерживаются редакторами кода. + +!!! check "**FastAPI** использует Pydantic" + Для проверки данных, сериализации данных и автоматической документации моделей (на основе JSON Schema). + + Затем **FastAPI** берёт эти схемы JSON и помещает их в схему OpenAPI, не касаясь других вещей, которые он делает. + +### Starlette + +Starlette - это легковесный ASGI фреймворк/набор инструментов, который идеален для построения высокопроизводительных асинхронных сервисов. + +Starlette очень простой и интуитивный. +Он разработан таким образом, чтобы быть легко расширяемым и иметь модульные компоненты. + +В нём есть: + +* Впечатляющая производительность. +* Поддержка веб-сокетов. +* Фоновые задачи. +* Обработка событий при старте и финише приложения. +* Тестовый клиент на основе HTTPX. +* Поддержка CORS, сжатие GZip, статические файлы, потоковая передача данных. +* Поддержка сессий и куки. +* 100% покрытие тестами. +* 100% аннотированный код. +* Несколько жёстких зависимостей. + +В настоящее время Starlette показывает самую высокую скорость среди Python-фреймворков в тестовых замерах. +Быстрее только Uvicorn, который является сервером, а не фреймворком. + +Starlette обеспечивает весь функционал микрофреймворка, но не предоставляет автоматическую валидацию данных, сериализацию и документацию. + +**FastAPI** добавляет эти функции используя подсказки типов Python и Pydantic. +Ещё **FastAPI** добавляет систему внедрения зависимостей, утилиты безопасности, генерацию схемы OpenAPI и т.д. + +!!! note "Технические детали" + ASGI - это новый "стандарт" разработанный участниками команды Django. + Он пока что не является "стандартом в Python" (то есть принятым PEP), но процесс принятия запущен. + + Тем не менее он уже используется в качестве "стандарта" несколькими инструментами. + Это значительно улучшает совместимость, поскольку Вы можете переключиться с Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или Вы можете добавить ASGI-совместимые инструменты, такие как `python-socketio`. + +!!! check "**FastAPI** использует Starlette" + В качестве ядра веб-сервиса для обработки запросов, добавив некоторые функции сверху. + + Класс `FastAPI` наследуется напрямую от класса `Starlette`. + + Таким образом, всё что Вы могли делать со Starlette, Вы можете делать с **FastAPI**, по сути это прокачанный Starlette. + +### Uvicorn + +Uvicorn - это молниеносный ASGI-сервер, построенный на uvloop и httptools. + +Uvicorn является сервером, а не фреймворком. +Например, он не предоставляет инструментов для маршрутизации запросов по ресурсам. +Для этого нужна надстройка, такая как Starlette (или **FastAPI**). + +Он рекомендуется в качестве сервера для Starlette и **FastAPI**. + +!!! check "**FastAPI** рекомендует его" + Как основной сервер для запуска приложения **FastAPI**. + + Вы можете объединить его с Gunicorn, чтобы иметь асинхронный многопроцессный сервер. + + Узнать больше деталей можно в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}. + +## Тестовые замеры и скорость + +Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, ознакомьтесь с разделом [Тестовые замеры](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/benchmarks.md b/docs/ru/docs/benchmarks.md new file mode 100644 index 0000000000..259dca8e67 --- /dev/null +++ b/docs/ru/docs/benchmarks.md @@ -0,0 +1,37 @@ +# Замеры производительности + +Независимые тесты производительности приложений от TechEmpower показывают, что **FastAPI** под управлением Uvicorn один из самых быстрых Python-фреймворков и уступает только Starlette и Uvicorn (которые используются в FastAPI). (*) + +Но при просмотре и сравнении замеров производительности следует иметь в виду нижеописанное. + +## Замеры производительности и скорости + +В подобных тестах часто можно увидеть, что инструменты разного типа сравнивают друг с другом, как аналогичные. + +В частности, сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов). + +Чем проще проблема, которую решает инструмент, тем выше его производительность. И большинство тестов не проверяют дополнительные функции, предоставляемые инструментом. + +Иерархия инструментов имеет следующий вид: + +* **Uvicorn**: ASGI-сервер + * **Starlette** (использует Uvicorn): веб-микрофреймворк + * **FastAPI** (использует Starlette): API-микрофреймворк с дополнительными функциями для создания API, с валидацией данных и т.д. + +* **Uvicorn**: + * Будет иметь наилучшую производительность, так как не имеет большого количества дополнительного кода, кроме самого сервера. + * Вы не будете писать приложение на Uvicorn напрямую. Это означало бы, что Ваш код должен включать как минимум весь + код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки. + * Uvicorn подлежит сравнению с Daphne, Hypercorn, uWSGI и другими веб-серверами. + +* **Starlette**: + * Будет уступать Uvicorn по производительности. Фактически Starlette управляется Uvicorn и из-за выполнения большего количества кода он не может быть быстрее, чем Uvicorn. + * Зато он предоставляет Вам инструменты для создания простых веб-приложений с обработкой маршрутов URL и т.д. + * Starlette следует сравнивать с Sanic, Flask, Django и другими веб-фреймворками (или микрофреймворками). + +* **FastAPI**: + * Так же как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует Starlette, то есть он не может быть быстрее Starlette. + * FastAPI предоставляет больше возможностей поверх Starlette, которые наверняка Вам понадобятся при создании API, такие как проверка данных и сериализация. В довесок Вы ещё и получаете автоматическую документацию (автоматическая документация даже не увеличивает накладные расходы при работе приложения, так как она создается при запуске). + * Если Вы не используете FastAPI, а используете Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т.д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях. + * Таким образом, используя FastAPI Вы потратите меньше времени на разработку, уменьшите количество ошибок, строк кода и, вероятно, получите ту же производительность (или лучше), как и если бы не использовали его (поскольку Вам пришлось бы реализовать все его возможности в своем коде). + * FastAPI должно сравнивать с фреймворками веб-приложений (или наборами инструментов), которые обеспечивают валидацию и сериализацию данных, а также предоставляют автоматическую документацию, такими как Flask-apispec, NestJS, Molten и им подобные. diff --git a/docs/ru/docs/contributing.md b/docs/ru/docs/contributing.md new file mode 100644 index 0000000000..f9b8912e55 --- /dev/null +++ b/docs/ru/docs/contributing.md @@ -0,0 +1,469 @@ +# Участие в разработке фреймворка + +Возможно, для начала Вам стоит ознакомиться с основными способами [помочь FastAPI или получить помощь](help-fastapi.md){.internal-link target=_blank}. + +## Разработка + +Если Вы уже склонировали репозиторий и знаете, что Вам нужно более глубокое погружение в код фреймворка, то здесь представлены некоторые инструкции по настройке виртуального окружения. + +### Виртуальное окружение с помощью `venv` + +Находясь в нужной директории, Вы можете создать виртуальное окружение при помощи Python модуля `venv`. + +
+ +```console +$ python -m venv env +``` + +
+ +Эта команда создаст директорию `./env/` с бинарными (двоичными) файлами Python, а затем Вы сможете скачивать и устанавливать необходимые библиотеки в изолированное виртуальное окружение. + +### Активация виртуального окружения + +Активируйте виртуально окружение командой: + +=== "Linux, macOS" + +
+ + ```console + $ source ./env/bin/activate + ``` + +
+ +=== "Windows PowerShell" + +
+ + ```console + $ .\env\Scripts\Activate.ps1 + ``` + +
+ +=== "Windows Bash" + + Если Вы пользуетесь Bash для Windows (например: Git Bash): + +
+ + ```console + $ source ./env/Scripts/activate + ``` + +
+ +Проверьте, что всё сработало: + +=== "Linux, macOS, Windows Bash" + +
+ + ```console + $ which pip + + some/directory/fastapi/env/bin/pip + ``` + +
+ +=== "Windows PowerShell" + +
+ + ```console + $ Get-Command pip + + some/directory/fastapi/env/bin/pip + ``` + +
+ +Если в терминале появится ответ, что бинарник `pip` расположен по пути `.../env/bin/pip`, значит всё в порядке. 🎉 + +Во избежание ошибок в дальнейших шагах, удостоверьтесь, что в Вашем виртуальном окружении установлена последняя версия `pip`: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +!!! tip "Подсказка" + Каждый раз, перед установкой новой библиотеки в виртуальное окружение при помощи `pip`, не забудьте активировать это виртуальное окружение. + + Это гарантирует, что если Вы используете библиотеку, установленную этим пакетом, то Вы используете библиотеку из Вашего локального окружения, а не любую другую, которая может быть установлена глобально. + +### pip + +После активации виртуального окружения, как было указано ранее, введите следующую команду: + +
+ +```console +$ pip install -r requirements.txt + +---> 100% +``` + +
+ +Это установит все необходимые зависимости в локальное окружение для Вашего локального FastAPI. + +#### Использование локального FastAPI + +Если Вы создаёте Python файл, который импортирует и использует FastAPI,а затем запускаете его интерпретатором Python из Вашего локального окружения, то он будет использовать код из локального FastAPI. + +И, так как при вводе вышеупомянутой команды был указан флаг `-e`, если Вы измените код локального FastAPI, то при следующем запуске этого файла, он будет использовать свежую версию локального FastAPI, который Вы только что изменили. + +Таким образом, Вам не нужно "переустанавливать" Вашу локальную версию, чтобы протестировать каждое изменение. + +### Форматировние + +Скачанный репозиторий содержит скрипт, который может отформатировать и подчистить Ваш код: + +
+ +```console +$ bash scripts/format.sh +``` + +
+ +Заодно он упорядочит Ваши импорты. + +Чтобы он сортировал их правильно, необходимо, чтобы FastAPI был установлен локально в Вашей среде, с помощью команды из раздела выше, использующей флаг `-e`. + +## Документация + +Прежде всего, убедитесь, что Вы настроили своё окружение, как описано выше, для установки всех зависимостей. + +Документация использует MkDocs. + +Также существуют дополнительные инструменты/скрипты для работы с переводами в `./scripts/docs.py`. + +!!! tip "Подсказка" + + Нет необходимости заглядывать в `./scripts/docs.py`, просто используйте это в командной строке. + +Вся документация имеет формат Markdown и расположена в директории `./docs/en/`. + +Многие руководства содержат блоки кода. + +В большинстве случаев эти блоки кода представляют собой вполне законченные приложения, которые можно запускать как есть. + +На самом деле, эти блоки кода не написаны внутри Markdown, это Python файлы в директории `./docs_src/`. + +И эти Python файлы включаются/вводятся в документацию при создании сайта. + +### Тестирование документации + + +Фактически, большинство тестов запускаются с примерами исходных файлов в документации. + +Это помогает убедиться, что: + +* Документация находится в актуальном состоянии. +* Примеры из документации могут быть запущены как есть. +* Большинство функций описаны в документации и покрыты тестами. + +Существует скрипт, который во время локальной разработки создаёт сайт и проверяет наличие любых изменений, перезагружая его в реальном времени: + +
+ +```console +$ python ./scripts/docs.py live + +[INFO] Serving on http://127.0.0.1:8008 +[INFO] Start watching changes +[INFO] Start detecting changes +``` + +
+ +Он запустит сайт документации по адресу: `http://127.0.0.1:8008`. + + +Таким образом, Вы сможете редактировать файлы с документацией или кодом и наблюдать изменения вживую. + +#### Typer CLI (опционально) + + +Приведенная ранее инструкция показала Вам, как запускать скрипт `./scripts/docs.py` непосредственно через интерпретатор `python` . + +Но также можно использовать Typer CLI, что позволит Вам воспользоваться автозаполнением команд в Вашем терминале. + +Если Вы установили Typer CLI, то для включения функции автозаполнения, введите эту команду: + +
+ +```console +$ typer --install-completion + +zsh completion installed in /home/user/.bashrc. +Completion will take effect once you restart the terminal. +``` + +
+ +### Приложения и документация одновременно + +Если Вы запускаете приложение, например так: + +
+ +```console +$ uvicorn tutorial001:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +По умолчанию Uvicorn будет использовать порт `8000` и не будет конфликтовать с сайтом документации, использующим порт `8008`. + +### Переводы на другие языки + +Помощь с переводами ценится КРАЙНЕ ВЫСОКО! И переводы не могут быть сделаны без помощи сообщества. 🌎 🚀 + +Ниже приведены шаги, как помочь с переводами. + +#### Подсказки и инструкции + +* Проверьте существующие пул-реквесты для Вашего языка. Добавьте отзывы с просьбой внести изменения, если они необходимы, или одобрите их. + +!!! tip "Подсказка" + Вы можете добавлять комментарии с предложениями по изменению в существующие пул-реквесты. + + Ознакомьтесь с документацией о добавлении отзыва к пул-реквесту, чтобы утвердить его или запросить изменения. + +* Проверьте проблемы и вопросы, чтобы узнать, есть ли кто-то, координирующий переводы для Вашего языка. + +* Добавляйте один пул-реквест для каждой отдельной переведённой страницы. Это значительно облегчит другим его просмотр. + +Для языков, которые я не знаю, прежде чем добавить перевод в основную ветку, я подожду пока несколько других участников сообщества проверят его. + +* Вы также можете проверить, есть ли переводы для Вашего языка и добавить к ним отзыв, который поможет мне убедиться в правильности перевода. Тогда я смогу объединить его с основной веткой. + +* Используйте те же самые примеры кода Python. Переводите только текст документации. Вам не нужно ничего менять, чтобы эти примеры работали. + +* Используйте те же самые изображения, имена файлов и ссылки. Вы не должны менять ничего для сохранения работоспособности. + +* Чтобы узнать 2-буквенный код языка, на который Вы хотите сделать перевод, Вы можете воспользоваться таблицей Список кодов языков ISO 639-1. + +#### Существующий язык + +Допустим, Вы хотите перевести страницу на язык, на котором уже есть какие-то переводы, например, на испанский. + +Кодом испанского языка является `es`. А значит директория для переводов на испанский язык: `docs/es/`. + +!!! tip "Подсказка" + Главный ("официальный") язык - английский, директория для него `docs/en/`. + +Вы можете запустить сервер документации на испанском: + +
+ +```console +// Используйте команду "live" и передайте код языка в качестве аргумента командной строки +$ python ./scripts/docs.py live es + +[INFO] Serving on http://127.0.0.1:8008 +[INFO] Start watching changes +[INFO] Start detecting changes +``` + +
+ +Теперь Вы можете перейти по адресу: http://127.0.0.1:8008 и наблюдать вносимые Вами изменения вживую. + + +Если Вы посмотрите на сайт документации FastAPI, то увидите, что все страницы есть на каждом языке. Но некоторые страницы не переведены и имеют уведомление об отсутствующем переводе. + +Но когда Вы запускаете сайт локально, Вы видите только те страницы, которые уже переведены. + + +Предположим, что Вы хотите добавить перевод страницы [Основные свойства](features.md){.internal-link target=_blank}. + +* Скопируйте файл: + +``` +docs/en/docs/features.md +``` + +* Вставьте его точно в то же место, но в директорию языка, на который Вы хотите сделать перевод, например: + +``` +docs/es/docs/features.md +``` + +!!! tip "Подсказка" + Заметьте, что в пути файла мы изменили только код языка с `en` на `es`. + +* Теперь откройте файл конфигурации MkDocs для английского языка, расположенный тут: + +``` +docs/en/mkdocs.yml +``` + +* Найдите в файле конфигурации место, где расположена строка `docs/features.md`. Похожее на это: + +```YAML hl_lines="8" +site_name: FastAPI +# More stuff +nav: +- FastAPI: index.md +- Languages: + - en: / + - es: /es/ +- features.md +``` + +* Откройте файл конфигурации MkDocs для языка, на который Вы переводите, например: + +``` +docs/es/mkdocs.yml +``` + +* Добавьте строку `docs/features.md` точно в то же место, как и в случае для английского, как-то так: + +```YAML hl_lines="8" +site_name: FastAPI +# More stuff +nav: +- FastAPI: index.md +- Languages: + - en: / + - es: /es/ +- features.md +``` + +Убедитесь, что при наличии других записей, новая запись с Вашим переводом находится точно в том же порядке, что и в английской версии. + +Если Вы зайдёте в свой браузер, то увидите, что в документации стал отображаться Ваш новый раздел.🎉 + +Теперь Вы можете переводить эту страницу и смотреть, как она выглядит при сохранении файла. + +#### Новый язык + +Допустим, Вы хотите добавить перевод для языка, на который пока что не переведена ни одна страница. + +Скажем, Вы решили сделать перевод для креольского языка, но его еще нет в документации. + +Перейдите в таблицу кодов языков по ссылке указанной выше, где найдёте, что кодом креольского языка является `ht`. + +Затем запустите скрипт, генерирующий директорию для переводов на новые языки: + +
+ +```console +// Используйте команду new-lang и передайте код языка в качестве аргумента командной строки +$ python ./scripts/docs.py new-lang ht + +Successfully initialized: docs/ht +Updating ht +Updating en +``` + +
+ +После чего Вы можете проверить в своем редакторе кода, что появился новый каталог `docs/ht/`. + +!!! tip "Подсказка" + Создайте первый пул-реквест, который будет содержать только пустую директорию для нового языка, прежде чем добавлять переводы. + + Таким образом, другие участники могут переводить другие страницы, пока Вы работаете над одной. 🚀 + +Начните перевод с главной страницы `docs/ht/index.md`. + +В дальнейшем можно действовать, как указано в предыдущих инструкциях для "существующего языка". + +##### Новый язык не поддерживается + +Если при запуске скрипта `./scripts/docs.py live` Вы получаете сообщение об ошибке, что язык не поддерживается, что-то вроде: + +``` + raise TemplateNotFound(template) +jinja2.exceptions.TemplateNotFound: partials/language/xx.html +``` + +Сие означает, что тема не поддерживает этот язык (в данном случае с поддельным 2-буквенным кодом `xx`). + +Но не стоит переживать. Вы можете установить языком темы английский, а затем перевести текст документации. + +Если возникла такая необходимость, отредактируйте `mkdocs.yml` для Вашего нового языка. Это будет выглядеть как-то так: + +```YAML hl_lines="5" +site_name: FastAPI +# More stuff +theme: + # More stuff + language: xx +``` + +Измените `xx` (код Вашего языка) на `en` и перезапустите сервер. + +#### Предпросмотр результата + +Когда Вы запускаете скрипт `./scripts/docs.py` с командой `live`, то будут показаны файлы и переводы для указанного языка. + +Но когда Вы закончите, то можете посмотреть, как это будет выглядеть по-настоящему. + +Для этого сначала создайте всю документацию: + +
+ +```console +// Используйте команду "build-all", это займёт немного времени +$ python ./scripts/docs.py build-all + +Updating es +Updating en +Building docs for: en +Building docs for: es +Successfully built docs for: es +Copying en index.md to README.md +``` + +
+ +Скрипт сгенерирует `./docs_build/` для каждого языка. Он добавит все файлы с отсутствующими переводами с пометкой о том, что "у этого файла еще нет перевода". Но Вам не нужно ничего делать с этим каталогом. + +Затем он создаст независимые сайты MkDocs для каждого языка, объединит их и сгенерирует конечный результат на `./site/`. + +После чего Вы сможете запустить сервер со всеми языками командой `serve`: + +
+ +```console +// Используйте команду "serve" после того, как отработает команда "build-all" +$ python ./scripts/docs.py serve + +Warning: this is a very simple server. For development, use mkdocs serve instead. +This is here only to preview a site with translations already built. +Make sure you run the build-all command first. +Serving at: http://127.0.0.1:8008 +``` + +
+ +## Тесты + +Также в репозитории есть скрипт, который Вы можете запустить локально, чтобы протестировать весь код и сгенерировать отчеты о покрытии тестами в HTML: + +
+ +```console +$ bash scripts/test-cov-html.sh +``` + +
+ +Эта команда создаст директорию `./htmlcov/`, в которой будет файл `./htmlcov/index.html`. Открыв его в Вашем браузере, Вы можете в интерактивном режиме изучить, все ли части кода охвачены тестами. diff --git a/docs/ru/docs/deployment/concepts.md b/docs/ru/docs/deployment/concepts.md new file mode 100644 index 0000000000..681acf15ea --- /dev/null +++ b/docs/ru/docs/deployment/concepts.md @@ -0,0 +1,311 @@ +# Концепции развёртывания + +Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых Вы можете выбрать **наиболее подходящий** способ. + +Самые важные из них: + +* Использование более безопасного протокола HTTPS +* Настройки запуска приложения +* Перезагрузка приложения +* Запуск нескольких экземпляров приложения +* Управление памятью +* Использование перечисленных функций перед запуском приложения. + +Рассмотрим ниже влияние каждого из них на процесс **развёртывания**. + +Наша конечная цель - **обслуживать клиентов Вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀 + +Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у Вас сложится **интуитивное понимание**, какой способ выбрать при развертывании Вашего API в различных окружениях, возможно, даже **ещё не существующих**. + +Ознакомившись с этими концепциями, Вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**. + +В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI. + +А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡 + +## Использование более безопасного протокола HTTPS + +В [предыдущей главе об HTTPS](./https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для Вашего API. + +Также мы заметили, что обычно для работы с HTTPS Вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**. + +И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия. + +### Примеры инструментов для работы с HTTPS + +Вот некоторые инструменты, которые Вы можете применять как прокси-серверы: + +* Traefik + * С автоматическим обновлением сертификатов ✨ +* Caddy + * С автоматическим обновлением сертификатов ✨ +* Nginx + * С дополнительным компонентом типа Certbot для обновления сертификатов +* HAProxy + * С дополнительным компонентом типа Certbot для обновления сертификатов +* Kubernetes с Ingress Controller похожим на Nginx + * С дополнительным компонентом типа cert-manager для обновления сертификатов +* Использование услуг облачного провайдера (читайте ниже 👇) + +В последнем варианте Вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера. + +В дальнейшем я покажу Вам некоторые конкретные примеры их применения. + +--- + +Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn). + +## Программа и процесс + +Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними. + +### Что такое программа + +Термином **программа** обычно описывают множество вещей: + +* **Код**, который Вы написали, в нашем случае **Python-файлы**. +* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`. +* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**. + +### Что такое процесс + +Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца): + +* Конкретная программа, **запущенная** операционной системой. + * Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой. +* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**. +* Процесс может быть **прерван** (или "убит") Вами или Вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**. +* Каждое приложение, которое Вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов. +* И **одна программа** может запустить **несколько параллельных процессов**. + +Если Вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) Вашей операционной системы, то увидите множество работающих процессов. + +Вполне вероятно, что Вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы. + + + +--- + +Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания. + +## Настройки запуска приложения + +В большинстве случаев когда Вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у Вас могут быть причины, чтоб оно запускалось только при определённых условиях. + +### Удалённый сервер + +Когда Вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как Вы делаете при локальной разработке. + +Это рабочий способ и он полезен **во время разработки**. + +Но если Вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**. + +И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), Вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱 + +### Автоматический запуск программ + +Вероятно Вы пожелаете, чтоб Ваша серверная программа (такая как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI). + +### Отдельная программа + +Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных + +### Примеры инструментов, управляющих запуском программ + +Вот несколько примеров, которые могут справиться с такой задачей: + +* Docker +* Kubernetes +* Docker Compose +* Docker в режиме Swarm +* Systemd +* Supervisor +* Использование услуг облачного провайдера +* Прочие... + +Я покажу Вам некоторые примеры их использования в следующих главах. + +## Перезапуск + +Вы, вероятно, также пожелаете, чтоб Ваше приложение **перезапускалось**, если в нём произошёл сбой. + +### Мы ошибаемся + +Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛 + +И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅). + +### Небольшие ошибки обрабатываются автоматически + +Когда Вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡 + +Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами. + +### Большие ошибки - Падение приложений + +Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥 + +Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок. + +### Перезапуск после падения + +Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз... + +!!! tip "Заметка" + ... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, Вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания. + + Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново. + +Возможно Вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск Вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в Вашем коде внутри приложения, не может быть выполнено в принципе. + +### Примеры инструментов для автоматического перезапуска + +В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы. + +В качестве примера можно взять те же: + +* Docker +* Kubernetes +* Docker Compose +* Docker в режиме Swarm +* Systemd +* Supervisor +* Использование услуг облачного провайдера +* Прочие... + +## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память + +Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно. + +Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов. + +### Множество процессов - Воркеры (Workers) + +Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то Вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами. + +**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**. + +### Процессы и порты́ + +Помните ли Вы, как на странице [Об HTTPS](./https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта? + +С тех пор ничего не изменилось. + +Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу. + +### У каждого процесса своя память + +Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера. + +Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения Вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти. + +### Память сервера + +Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда Вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если Вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате Вашему API потребуется **4 ГБ оперативной памяти (RAM)**. + +И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨 + +### Множество процессов - Пример + +В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**. + +Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам. + +Каждый из этих процессов будет запускать Ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память. + + + +Безусловно, на этом же сервере будут работать и **другие процессы**, которые не относятся к Вашему приложению. + +Интересная деталь - обычно в течение времени процент **использования центрального процессора (CPU)** каждым процессом может очень сильно **изменяться**, но объём занимаемой **оперативной памяти (RAM)** остаётся относительно **стабильным**. + +Если у Вас есть API, который каждый раз выполняет сопоставимый объем вычислений, и у Вас много клиентов, то **загрузка процессора**, вероятно, *также будет стабильной* (вместо того, чтобы постоянно быстро увеличиваться и уменьшаться). + +### Примеры стратегий и инструментов для запуска нескольких экземпляров приложения + +Существует несколько подходов для достижения целей репликации и я расскажу Вам больше о конкретных стратегиях в следующих главах, например, когда речь пойдет о Docker и контейнерах. + +Основное ограничение при этом - только **один** компонент может работать с определённым **портом публичного IP**. И должен быть способ **передачи** данных между этим компонентом и копиями **процессов/воркеров**. + +Вот некоторые возможные комбинации и стратегии: + +* **Gunicorn** управляющий **воркерами Uvicorn** + * Gunicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **множества работающих процессов Uvicorn**. +* **Uvicorn** управляющий **воркерами Uvicorn** + * Один процесс Uvicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Он будет запускать **множество работающих процессов Uvicorn**. +* **Kubernetes** и аналогичные **контейнерные системы** + * Какой-то компонент в **Kubernetes** будет слушать **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**. +* **Облачные сервисы**, которые позаботятся обо всём за Вас + * Возможно, что облачный сервис умеет **управлять запуском дополнительных экземпляров приложения**. Вероятно, он потребует, чтоб Вы указали - какой **процесс** или **образ** следует клонировать. Скорее всего, Вы укажете **один процесс Uvicorn** и облачный сервис будет запускать его копии при необходимости. + +!!! tip "Заметка" + Если Вы не знаете, что такое **контейнеры**, Docker или Kubernetes, не переживайте. + + Я поведаю Вам о контейнерах, образах, Docker, Kubernetes и т.п. в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}. + +## Шаги, предшествующие запуску + +Часто бывает, что Вам необходимо произвести какие-то подготовительные шаги **перед запуском** своего приложения. + +Например, запустить **миграции базы данных**. + +Но в большинстве случаев такие действия достаточно произвести **однократно**. + +Поэтому Вам нужен будет **один процесс**, выполняющий эти **подготовительные шаги** до запуска приложения. + +Также Вам нужно будет убедиться, что этот процесс выполнил подготовительные шаги *даже* если впоследствии Вы запустите **несколько процессов** (несколько воркеров) самого приложения. Если бы эти шаги выполнялись в каждом **клонированном процессе**, они бы **дублировали** работу, пытаясь выполнить её **параллельно**. И если бы эта работа была бы чем-то деликатным, вроде миграции базы данных, то это может вызвать конфликты между ними. + +Безусловно, возможны случаи, когда нет проблем при выполнении предварительной подготовки параллельно или несколько раз. Тогда Вам повезло, работать с ними намного проще. + +!!! tip "Заметка" + Имейте в виду, что в некоторых случаях запуск Вашего приложения **может не требовать каких-либо предварительных шагов вовсе**. + + Что ж, тогда Вам не нужно беспокоиться об этом. 🤷 + +### Примеры стратегий запуска предварительных шагов + +Существует **сильная зависимость** от того, как Вы **развёртываете свою систему**, запускаете программы, обрабатываете перезапуски и т.д. + +Вот некоторые возможные идеи: + +* При использовании Kubernetes нужно предусмотреть "инициализирующий контейнер", запускаемый до контейнера с приложением. +* Bash-скрипт, выполняющий предварительные шаги, а затем запускающий приложение. + * При этом Вам всё ещё нужно найти способ - как запускать/перезапускать *такой* bash-скрипт, обнаруживать ошибки и т.п. + +!!! tip "Заметка" + Я приведу Вам больше конкретных примеров работы с контейнерами в главе: [FastAPI внутри контейнеров - Docker](./docker.md){.internal-link target=_blank}. + +## Утилизация ресурсов + +Ваш сервер располагает ресурсами, которые Ваши программы могут потреблять или **утилизировать**, а именно - время работы центрального процессора и объём оперативной памяти. + +Как много системных ресурсов Вы предполагаете потребить/утилизировать? Если не задумываться, то можно ответить - "немного", но на самом деле Вы, вероятно, пожелаете использовать **максимально возможное количество**. + +Если Вы платите за содержание трёх серверов, но используете лишь малую часть системных ресурсов каждого из них, то Вы **выбрасываете деньги на ветер**, а также **впустую тратите электроэнергию** и т.п. + +В таком случае было бы лучше обойтись двумя серверами, но более полно утилизировать их ресурсы (центральный процессор, оперативную память, жёсткий диск, сети передачи данных и т.д). + +С другой стороны, если Вы располагаете только двумя серверами и используете **на 100% их процессоры и память**, но какой-либо процесс запросит дополнительную память, то операционная система сервера будет использовать жёсткий диск для расширения оперативной памяти (а диск работает в тысячи раз медленнее), а то вовсе **упадёт**. Или если какому-то процессу понадобится произвести вычисления, то ему придётся подождать, пока процессор освободится. + +В такой ситуации лучше подключить **ещё один сервер** и перераспределить процессы между серверами, чтоб всем **хватало памяти и процессорного времени**. + +Также есть вероятность, что по какой-то причине возник **всплеск** запросов к Вашему API. Возможно, это был вирус, боты или другие сервисы начали пользоваться им. И для таких происшествий Вы можете захотеть иметь дополнительные ресурсы. + +При настройке логики развёртываний, Вы можете указать **целевое значение** утилизации ресурсов, допустим, **от 50% до 90%**. Обычно эти метрики и используют. + +Вы можете использовать простые инструменты, такие как `htop`, для отслеживания загрузки центрального процессора и оперативной памяти сервера, в том числе каждым процессом. Или более сложные системы мониторинга нескольких серверов. + +## Резюме + +Вы прочитали некоторые из основных концепций, которые необходимо иметь в виду при принятии решения о развертывании приложений: + +* Использование более безопасного протокола HTTPS +* Настройки запуска приложения +* Перезагрузка приложения +* Запуск нескольких экземпляров приложения +* Управление памятью +* Использование перечисленных функций перед запуском приложения. + +Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓 + +В следующих разделах я приведу более конкретные примеры возможных стратегий, которым Вы можете следовать. 🚀 diff --git a/docs/ru/docs/deployment/https.md b/docs/ru/docs/deployment/https.md new file mode 100644 index 0000000000..a53ab69272 --- /dev/null +++ b/docs/ru/docs/deployment/https.md @@ -0,0 +1,198 @@ +# Об HTTPS + +Обычно представляется, что HTTPS это некая опция, которая либо "включена", либо нет. + +Но всё несколько сложнее. + +!!! tip "Заметка" + Если Вы торопитесь или Вам не интересно, можете перейти на следующую страницу этого пошагового руководства по размещению приложений на серверах с использованием различных технологий. + +Чтобы **изучить основы HTTPS** для клиента, перейдите по ссылке https://howhttps.works/. + +Здесь же представлены некоторые концепции, которые **разработчик** должен иметь в виду при размышлениях об HTTPS: + +* Протокол HTTPS предполагает, что **серверу** нужно **располагать "сертификатами"** сгенерированными **третьей стороной**. + * На самом деле эти сертификаты **приобретены** у третьей стороны, а не "сгенерированы". +* У сертификатов есть **срок годности**. + * Срок годности **истекает**. + * По истечении срока годности их нужно **обновить**, то есть **снова получить** у третьей стороны. +* Шифрование соединения происходит **на уровне протокола TCP**. + * Протокол TCP находится на один уровень **ниже протокола HTTP**. + * Поэтому **проверка сертификатов и шифрование** происходит **до HTTP**. +* **TCP не знает о "доменах"**, но знает об IP-адресах. + * Информация о **запрашиваемом домене** извлекается из запроса **на уровне HTTP**. +* **Сертификаты HTTPS** "сертифицируют" **конкретный домен**, но проверка сертификатов и шифрование данных происходит на уровне протокола TCP, то есть **до того**, как станет известен домен-получатель данных. +* **По умолчанию** это означает, что у Вас может быть **только один сертификат HTTPS на один IP-адрес**. + * Не важно, насколько большой у Вас сервер и насколько маленькие приложения на нём могут быть. + * Однако, у этой проблемы есть **решение**. +* Существует **расширение** протокола **TLS** (который работает на уровне TCP, то есть до HTTP) называемое **SNI**. + * Расширение SNI позволяет одному серверу (с **одним IP-адресом**) иметь **несколько сертификатов HTTPS** и обслуживать **множество HTTPS-доменов/приложений**. + * Чтобы эта конструкция работала, **один** её компонент (программа) запущенный на сервере и слушающий **публичный IP-адрес**, должен иметь **все сертификаты HTTPS** для этого сервера. +* **После** установления защищённого соединения, протоколом передачи данных **остаётся HTTP**. + * Но данные теперь **зашифрованы**, несмотря на то, что они передаются по **протоколу HTTP**. + +Обычной практикой является иметь **одну программу/HTTP-сервер** запущенную на сервере (машине, хосте и т.д.) и **ответственную за всю работу с HTTPS**: + +* получение **зашифрованных HTTPS-запросов** +* отправка **расшифрованных HTTP запросов** в соответствующее HTTP-приложение, работающее на том же сервере (в нашем случае, это приложение **FastAPI**) +* получние **HTTP-ответа** от приложения +* **шифрование ответа** используя подходящий **сертификат HTTPS** +* отправка зашифрованного **HTTPS-ответа клиенту**. +Такой сервер часто называют **Прокси-сервер завершения работы TLS** или просто "прокси-сервер". + +Вот некоторые варианты, которые Вы можете использовать в качестве такого прокси-сервера: + +* Traefik (может обновлять сертификаты) +* Caddy (может обновлять сертификаты) +* Nginx +* HAProxy + +## Let's Encrypt (центр сертификации) + +До появления Let's Encrypt **сертификаты HTTPS** приходилось покупать у третьих сторон. + +Процесс получения такого сертификата был трудоёмким, требовал предоставления подтверждающих документов и сертификаты стоили дорого. + +Но затем консорциумом Linux Foundation был создан проект **Let's Encrypt**. + +Он автоматически предоставляет **бесплатные сертификаты HTTPS**. Эти сертификаты используют все стандартные криптографические способы шифрования. Они имеют небольшой срок годности (около 3 месяцев), благодаря чему они даже **более безопасны**. + +При запросе на получение сертификата, он автоматически генерируется и домен проверяется на безопасность. Это позволяет обновлять сертификаты автоматически. + +Суть идеи в автоматическом получении и обновлении этих сертификатов, чтобы все могли пользоваться **безопасным HTTPS. Бесплатно. В любое время.** + +## HTTPS для разработчиков + +Ниже, шаг за шагом, с заострением внимания на идеях, важных для разработчика, описано, как может выглядеть HTTPS API. + +### Имя домена + +Чаще всего, всё начинается с **приобретения имени домена**. Затем нужно настроить DNS-сервер (вероятно у того же провайдера, который выдал Вам домен). + +Далее, возможно, Вы получаете "облачный" сервер (виртуальную машину) или что-то типа этого, у которого есть постоянный **публичный IP-адрес**. + +На DNS-сервере (серверах) Вам следует настроить соответствующую ресурсную запись ("`запись A`"), указав, что **Ваш домен** связан с публичным **IP-адресом Вашего сервера**. + +Обычно эту запись достаточно указать один раз, при первоначальной настройке всего сервера. + +!!! tip "Заметка" + Уровни протоколов, работающих с именами доменов, намного ниже HTTPS, но об этом следует упомянуть здесь, так как всё зависит от доменов и IP-адресов. + +### DNS + +Теперь давайте сфокусируемся на работе с HTTPS. + +Всё начинается с того, что браузер спрашивает у **DNS-серверов**, какой **IP-адрес связан с доменом**, для примера возьмём домен `someapp.example.com`. + +DNS-сервера присылают браузеру определённый **IP-адрес**, тот самый публичный IP-адрес Вашего сервера, который Вы указали в ресурсной "записи А" при настройке. + + + +### Рукопожатие TLS + +В дальнейшем браузер будет взаимодействовать с этим IP-адресом через **port 443** (общепринятый номер порта для HTTPS). + +Первым шагом будет установление соединения между клиентом (браузером) и сервером и выбор криптографического ключа (для шифрования). + + + +Эта часть клиент-серверного взаимодействия устанавливает TLS-соединение и называется **TLS-рукопожатием**. + +### TLS с расширением SNI + +На сервере **только один процесс** может прослушивать определённый **порт** определённого **IP-адреса**. На сервере могут быть и другие процессы, слушающие другие порты этого же IP-адреса, но никакой процесс не может быть привязан к уже занятой комбинации IP-адрес:порт. Эта комбинация называется "сокет". + +По умолчанию TLS (HTTPS) использует порт `443`. Потому этот же порт будем использовать и мы. + +И раз уж только один процесс может слушать этот порт, то это будет процесс **прокси-сервера завершения работы TLS**. + +Прокси-сервер завершения работы TLS будет иметь доступ к одному или нескольким **TLS-сертификатам** (сертификаты HTTPS). + +Используя **расширение SNI** упомянутое выше, прокси-сервер из имеющихся сертификатов TLS (HTTPS) выберет тот, который соответствует имени домена, указанному в запросе от клиента. + +То есть будет выбран сертификат для домена `someapp.example.com`. + + + +Клиент уже **доверяет** тому, кто выдал этот TLS-сертификат (в нашем случае - Let's Encrypt, но мы ещё обсудим это), потому может **проверить**, действителен ли полученный от сервера сертификат. + +Затем, используя этот сертификат, клиент и прокси-сервер **выбирают способ шифрования** данных для устанавливаемого **TCP-соединения**. На этом операция **TLS-рукопожатия** завершена. + +В дальнейшем клиент и сервер будут взаимодействовать по **зашифрованному TCP-соединению**, как предлагается в протоколе TLS. И на основе этого TCP-соедениния будет создано **HTTP-соединение**. + +Таким образом, **HTTPS** это тот же **HTTP**, но внутри **безопасного TLS-соединения** вместо чистого (незашифрованного) TCP-соединения. + +!!! tip "Заметка" + Обратите внимание, что шифрование происходит на **уровне TCP**, а не на более высоком уровне HTTP. + +### HTTPS-запрос + +Теперь, когда между клиентом и сервером (в нашем случае, браузером и прокси-сервером) создано **зашифрованное TCP-соединение**, они могут начать **обмен данными по протоколу HTTP**. + +Так клиент отправляет **HTTPS-запрос**. То есть обычный HTTP-запрос, но через зашифрованное TLS-содинение. + + + +### Расшифровка запроса + +Прокси-сервер, используя согласованный с клиентом ключ, расшифрует полученный **зашифрованный запрос** и передаст **обычный (незашифрованный) HTTP-запрос** процессу, запускающему приложение (например, процессу Uvicorn запускающему приложение FastAPI). + + + +### HTTP-ответ + +Приложение обработает запрос и вернёт **обычный (незашифрованный) HTTP-ответ** прокси-серверу. + + + +### HTTPS-ответ + +Пркоси-сервер **зашифрует ответ** используя ранее согласованный с клиентом способ шифрования (которые содержатся в сертификате для домена `someapp.example.com`) и отправит его браузеру. + +Наконец, браузер проверит ответ, в том числе, что тот зашифрован с нужным ключом, **расшифрует его** и обработает. + + + +Клиент (браузер) знает, что ответ пришёл от правильного сервера, так как использует методы шифрования, согласованные ими раннее через **HTTPS-сертификат**. + +### Множество приложений + +На одном и том же сервере (или серверах) можно разместить **множество приложений**, например, другие программы с API или базы данных. + +Напомню, что только один процесс (например, прокси-сервер) может прослушивать определённый порт определённого IP-адреса. +Но другие процессы и приложения тоже могут работать на этом же сервере (серверах), если они не пытаются использовать уже занятую **комбинацию IP-адреса и порта** (сокет). + + + +Таким образом, сервер завершения TLS может обрабатывать HTTPS-запросы и использовать сертификаты для **множества доменов** или приложений и передавать запросы правильным адресатам (другим приложениям). + +### Обновление сертификата + +В недалёком будущем любой сертификат станет **просроченным** (примерно через три месяца после получения). + +Когда это произойдёт, можно запустить другую программу, которая подключится к Let's Encrypt и обновит сертификат(ы). Существуют прокси-серверы, которые могут сделать это действие самостоятельно. + + + +**TLS-сертификаты** не привязаны к IP-адресу, но **связаны с именем домена**. + +Так что при обновлении сертификатов программа должна **подтвердить** центру сертификации (Let's Encrypt), что обновление запросил **"владелец", который контролирует этот домен**. + +Есть несколько путей осуществления этого. Самые популярные из них: + +* **Изменение записей DNS**. + * Для такого варианта Ваша программа обновления должна уметь работать с API DNS-провайдера, обслуживающего Ваши DNS-записи. Не у всех провайдеров есть такой API, так что этот способ не всегда применим. +* **Запуск в качестве программы-сервера** (как минимум, на время обновления сертификатов) на публичном IP-адресе домена. + * Как уже не раз упоминалось, только один процесс может прослушивать определённый порт определённого IP-адреса. + * Это одна из причин использования прокси-сервера ещё и в качестве программы обновления сертификатов. + * В случае, если обновлением сертификатов занимается другая программа, Вам понадобится остановить прокси-сервер, запустить программу обновления сертификатов на сокете, предназначенном для прокси-сервера, настроить прокси-сервер на работу с новыми сертификатами и перезапустить его. Эта схема далека от идеальной, так как Ваши приложения будут недоступны на время отключения прокси-сервера. + +Весь этот процесс обновления, одновременный с обслуживанием запросов, является одной из основных причин, по которой желательно иметь **отдельную систему для работы с HTTPS** в виде прокси-сервера завершения TLS, а не просто использовать сертификаты TLS непосредственно с сервером приложений (например, Uvicorn). + +## Резюме + +Наличие **HTTPS** очень важно и довольно **критично** в большинстве случаев. Однако, Вам, как разработчику, не нужно тратить много сил на это, достаточно **понимать эти концепции** и принципы их работы. + +Но узнав базовые основы **HTTPS** Вы можете легко совмещать разные инструменты, которые помогут Вам в дальнейшей разработке. + +В следующих главах я покажу Вам несколько примеров, как настраивать **HTTPS** для приложений **FastAPI**. 🔒 diff --git a/docs/ru/docs/deployment/index.md b/docs/ru/docs/deployment/index.md new file mode 100644 index 0000000000..d214a9d62e --- /dev/null +++ b/docs/ru/docs/deployment/index.md @@ -0,0 +1,21 @@ +# Развёртывание + +Развернуть приложение **FastAPI** довольно просто. + +## Да что такое это ваше - "развёртывание"?! + +Термин **развёртывание** (приложения) означает выполнение необходимых шагов, чтобы сделать приложение **доступным для пользователей**. + +Обычно **веб-приложения** размещают на удалённом компьютере с серверной программой, которая обеспечивает хорошую производительность, стабильность и т. д., Чтобы ваши пользователи могли эффективно, беспрерывно и беспроблемно обращаться к приложению. + +Это отличется от **разработки**, когда вы постоянно меняете код, делаете в нём намеренные ошибки и исправляете их, останавливаете и перезапускаете сервер разработки и т. д. + +## Стратегии развёртывания + +В зависимости от вашего конкретного случая, есть несколько способов сделать это. + +Вы можете **развернуть сервер** самостоятельно, используя различные инструменты. Например, можно использовать **облачный сервис**, который выполнит часть работы за вас. Также возможны и другие варианты. + +В этом блоке я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений). + +В последующих разделах вы узнаете больше деталей и методов, необходимых для этого. ✨ diff --git a/docs/ru/docs/deployment/manually.md b/docs/ru/docs/deployment/manually.md new file mode 100644 index 0000000000..1d00b30860 --- /dev/null +++ b/docs/ru/docs/deployment/manually.md @@ -0,0 +1,150 @@ +# Запуск сервера вручную - Uvicorn + +Для запуска приложения **FastAPI** на удалённой серверной машине Вам необходим программный сервер, поддерживающий протокол ASGI, такой как **Uvicorn**. + +Существует три наиболее распространённые альтернативы: + +* Uvicorn: высокопроизводительный ASGI сервер. +* Hypercorn: ASGI сервер, помимо прочего поддерживающий HTTP/2 и Trio. +* Daphne: ASGI сервер, созданный для Django Channels. + +## Сервер как машина и сервер как программа + +В этих терминах есть некоторые различия и Вам следует запомнить их. 💡 + +Слово "**сервер**" чаще всего используется в двух контекстах: + +- удалённый или расположенный в "облаке" компьютер (физическая или виртуальная машина). +- программа, запущенная на таком компьютере (например, Uvicorn). + +Просто запомните, если Вам встретился термин "сервер", то обычно он подразумевает что-то из этих двух смыслов. + +Когда имеют в виду именно удалённый компьютер, часто говорят просто **сервер**, но ещё его называют **машина**, **ВМ** (виртуальная машина), **нода**. Все эти термины обозначают одно и то же - удалённый компьютер, обычно под управлением Linux, на котором Вы запускаете программы. + +## Установка программного сервера + +Вы можете установить сервер, совместимый с протоколом ASGI, так: + +=== "Uvicorn" + + * Uvicorn, молниесный ASGI сервер, основанный на библиотеках uvloop и httptools. + +
+ + ```console + $ pip install "uvicorn[standard]" + + ---> 100% + ``` + +
+ + !!! tip "Подсказка" + С опцией `standard`, Uvicorn будет установливаться и использоваться с некоторыми дополнительными рекомендованными зависимостями. + + В них входит `uvloop`, высокопроизводительная замена `asyncio`, которая значительно ускоряет работу асинхронных программ. + +=== "Hypercorn" + + * Hypercorn, ASGI сервер, поддерживающий протокол HTTP/2. + +
+ + ```console + $ pip install hypercorn + + ---> 100% + ``` + +
+ + ...или какой-либо другой ASGI сервер. + +## Запуск серверной программы + +Затем запустите Ваше приложение так же, как было указано в руководстве ранее, но без опции `--reload`: + +=== "Uvicorn" + +
+ + ```console + $ uvicorn main:app --host 0.0.0.0 --port 80 + + INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) + ``` + +
+ +=== "Hypercorn" + +
+ + ```console + $ hypercorn main:app --bind 0.0.0.0:80 + + Running on 0.0.0.0:8080 over http (CTRL + C to quit) + ``` + +
+ +!!! warning "Предупреждение" + + Не забудьте удалить опцию `--reload`, если ранее пользовались ею. + + Включение опции `--reload` требует дополнительных ресурсов, влияет на стабильность работы приложения и может повлечь прочие неприятности. + + Она сильно помогает во время **разработки**, но **не следует** использовать её при **реальной работе** приложения. + +## Hypercorn с Trio + +Starlette и **FastAPI** основаны на AnyIO, которая делает их совместимыми как с asyncio - стандартной библиотекой Python, так и с Trio. + + +Тем не менее Uvicorn совместим только с asyncio и обычно используется совместно с `uvloop`, высокопроизводительной заменой `asyncio`. + +Но если Вы хотите использовать **Trio** напрямую, то можете воспользоваться **Hypercorn**, так как они совместимы. ✨ + +### Установка Hypercorn с Trio + +Для начала, Вам нужно установить Hypercorn с поддержкой Trio: + +
+ +```console +$ pip install "hypercorn[trio]" +---> 100% +``` + +
+ +### Запуск с Trio + +Далее запустите Hypercorn с опцией `--worker-class` и аргументом `trio`: + +
+ +```console +$ hypercorn main:app --worker-class trio +``` + +
+ +Hypercorn, в свою очередь, запустит Ваше приложение использующее Trio. + +Таким образом, Вы сможете использовать Trio в своём приложении. Но лучше использовать AnyIO, для сохранения совместимости и с Trio, и с asyncio. 🎉 + +## Концепции развёртывания + +В вышеприведённых примерах серверные программы (например Uvicorn) запускали только **один процесс**, принимающий входящие запросы с любого IP (на это указывал аргумент `0.0.0.0`) на определённый порт (в примерах мы указывали порт `80`). + +Это основная идея. Но возможно, Вы озаботитесь добавлением дополнительных возможностей, таких как: + +* Использование более безопасного протокола HTTPS +* Настройки запуска приложения +* Перезагрузка приложения +* Запуск нескольких экземпляров приложения +* Управление памятью +* Использование перечисленных функций перед запуском приложения. + +Я поведаю Вам больше о каждой из этих концепций в следующих главах, с конкретными примерами стратегий работы с ними. 🚀 diff --git a/docs/ru/docs/deployment/versions.md b/docs/ru/docs/deployment/versions.md new file mode 100644 index 0000000000..91b9038e9f --- /dev/null +++ b/docs/ru/docs/deployment/versions.md @@ -0,0 +1,87 @@ +# О версиях FastAPI + +**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Однако его разработка все еще продолжается. + +Часто добавляются новые функции, регулярно исправляются баги, код продолжает постоянно совершенствоваться. + +По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя соглашению о Семантическом Версионировании. + +Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом. + +## Закрепите вашу версию `fastapi` + +Первым делом вам следует "закрепить" конкретную последнюю используемую версию **FastAPI**, которая корректно работает с вашим приложением. + +Например, в своём приложении вы используете версию `0.45.0`. + +Если вы используете файл `requirements.txt`, вы можете указать версию следующим способом: + +```txt +fastapi==0.45.0 +``` + +это означает, что вы будете использовать именно версию `0.45.0`. + +Или вы можете закрепить версию следующим способом: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +это значит, что вы используете версии `0.45.0` или выше, но меньше чем `0.46.0`. Например, версия `0.45.2` все еще будет подходить. + +Если вы используете любой другой инструмент для управления зависимостями, например Poetry, Pipenv или др., у них у всех имеется способ определения специфической версии для ваших пакетов. + +## Доступные версии + +Вы можете посмотреть доступные версии (например, проверить последнюю на данный момент) в [примечаниях к выпуску](../release-notes.md){.internal-link target=_blank}. + +## О версиях + +Следуя соглашению о Семантическом Версионировании, любые версии ниже `1.0.0` потенциально могут добавить обратно несовместимые изменения. + +FastAPI следует соглашению в том, что любые изменения "ПАТЧ"-версии предназначены для исправления багов и внесения обратно совместимых изменений. + +!!! Подсказка + "ПАТЧ" - это последнее число. Например, в `0.2.3`, ПАТЧ-версия - это `3`. + +Итак, вы можете закрепить версию следующим образом: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +Обратно несовместимые изменения и новые функции добавляются в "МИНОРНЫЕ" версии. + +!!! Подсказка + "МИНОРНАЯ" версия - это число в середине. Например, в `0.2.3` МИНОРНАЯ версия - это `2`. + +## Обновление версий FastAPI + +Вам следует добавить тесты для вашего приложения. + +С помощью **FastAPI** это очень просто (благодаря Starlette), см. документацию: [Тестирование](../tutorial/testing.md){.internal-link target=_blank} + +После создания тестов вы можете обновить свою версию **FastAPI** до более новой. После этого следует убедиться, что ваш код работает корректно, запустив тесты. + +Если все работает корректно, или после внесения необходимых изменений все ваши тесты проходят, только тогда вы можете закрепить вашу новую версию `fastapi`. + +## О Starlette + +Не следует закреплять версию `starlette`. + +Разные версии **FastAPI** будут использовать более новые версии Starlette. + +Так что решение об используемой версии Starlette, вы можете оставить **FastAPI**. + +## О Pydantic + +Pydantic включает свои собственные тесты для **FastAPI**, так что новые версии Pydantic (выше `1.0.0`) всегда совместимы с FastAPI. + +Вы можете закрепить любую версию Pydantic, которая вам подходит, выше `1.0.0` и ниже `2.0.0`. + +Например: + +```txt +pydantic>=1.2.0,<2.0.0 +``` diff --git a/docs/ru/docs/fastapi-people.md b/docs/ru/docs/fastapi-people.md new file mode 100644 index 0000000000..64ae66a035 --- /dev/null +++ b/docs/ru/docs/fastapi-people.md @@ -0,0 +1,180 @@ + +# Люди, поддерживающие FastAPI + +У FastAPI замечательное сообщество, которое доброжелательно к людям с любым уровнем знаний. + +## Создатель и хранитель + +Ку! 👋 + +Это я: + +{% if people %} +
+{% for user in people.maintainers %} + +
@{{ user.login }}
Answers: {{ user.answers }}
Pull Requests: {{ user.prs }}
+{% endfor %} + +
+{% endif %} + +Я создал и продолжаю поддерживать **FastAPI**. Узнать обо мне больше можно тут [Помочь FastAPI - Получить помощь - Связаться с автором](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}. + +... но на этой странице я хочу показать вам наше сообщество. + +--- + +**FastAPI** получает огромную поддержку от своего сообщества. И я хочу отметить вклад его участников. + +Это люди, которые: + +* [Помогают другим с их проблемами (вопросами) на GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}. +* [Создают пул-реквесты](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}. +* Делают ревью пул-реквестов, [что особенно важно для переводов на другие языки](contributing.md#translations){.internal-link target=_blank}. + +Поаплодируем им! 👏 🙇 + +## Самые активные участники за прошедший месяц + +Эти участники [оказали наибольшую помощь другим с решением их проблем (вопросов) на GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} в течение последнего месяца. ☕ + +{% if people %} +
+{% for user in people.last_month_active %} + +
@{{ user.login }}
Issues replied: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## Эксперты + +Здесь представлены **Эксперты FastAPI**. 🤓 + +Эти участники [оказали наибольшую помощь другим с решением их проблем (вопросов) на GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} за *всё время*. + +Оказывая помощь многим другим, они подтвердили свой уровень знаний. ✨ + +{% if people %} +
+{% for user in people.experts %} + +
@{{ user.login }}
Issues replied: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## Рейтинг участников, внёсших вклад в код + +Здесь представлен **Рейтинг участников, внёсших вклад в код**. 👷 + +Эти люди [сделали наибольшее количество пул-реквестов](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}, *включённых в основной код*. + +Они сделали наибольший вклад в исходный код, документацию, переводы и т.п. 📦 + +{% if people %} +
+{% for user in people.top_contributors %} + +
@{{ user.login }}
Pull Requests: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +На самом деле таких людей довольно много (более сотни), вы можете увидеть всех на этой странице FastAPI GitHub Contributors page. 👷 + +## Рейтинг ревьюеров + +Здесь представлен **Рейтинг ревьюеров**. 🕵️ + +### Проверки переводов на другие языки + +Я знаю не очень много языков (и не очень хорошо 😅). +Итак, ревьюеры - это люди, которые могут [**подтвердить предложенный вами перевод** документации](contributing.md#translations){.internal-link target=_blank}. Без них не было бы документации на многих языках. + +--- + +В **Рейтинге ревьюеров** 🕵️ представлены те, кто проверил наибольшее количество пул-реквестов других участников, обеспечивая качество кода, документации и, особенно, **переводов на другие языки**. + +{% if people %} +
+{% for user in people.top_reviewers %} + +
@{{ user.login }}
Reviews: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## Спонсоры + +Здесь представлены **Спонсоры**. 😎 + +Спонсоры поддерживают мою работу над **FastAPI** (и другими проектами) главным образом через GitHub Sponsors. + +{% if sponsors %} + +{% if sponsors.gold %} + +### Золотые спонсоры + +{% for sponsor in sponsors.gold -%} + +{% endfor %} +{% endif %} + +{% if sponsors.silver %} + +### Серебрянные спонсоры + +{% for sponsor in sponsors.silver -%} + +{% endfor %} +{% endif %} + +{% if sponsors.bronze %} + +### Бронзовые спонсоры + +{% for sponsor in sponsors.bronze -%} + +{% endfor %} +{% endif %} + +{% endif %} + +### Индивидуальные спонсоры + +{% if github_sponsors %} +{% for group in github_sponsors.sponsors %} + +
+ +{% for user in group %} +{% if user.login not in sponsors_badge.logins %} + + + +{% endif %} +{% endfor %} + +
+ +{% endfor %} +{% endif %} + +## О данных - технические детали + +Основная цель этой страницы - подчеркнуть усилия сообщества по оказанию помощи другим. + +Особенно это касается усилий, которые обычно менее заметны и во многих случаях более трудоемки, таких как помощь другим в решении проблем и проверка пул-реквестов с переводами. + +Данные рейтинги подсчитываются каждый месяц, ознакомиться с тем, как это работает можно тут. + +Кроме того, я также подчеркиваю вклад спонсоров. + +И я оставляю за собой право обновлять алгоритмы подсчёта, виды рейтингов, пороговые значения и т.д. (так, на всякий случай 🤷). diff --git a/docs/ru/docs/features.md b/docs/ru/docs/features.md new file mode 100644 index 0000000000..e18f7bc87c --- /dev/null +++ b/docs/ru/docs/features.md @@ -0,0 +1,203 @@ +# Основные свойства + +## Основные свойства FastAPI + +**FastAPI** предлагает вам следующее: + +### Использование открытых стандартов + +* OpenAPI для создания API, включая объявления операций пути, параметров, тела запроса, безопасности и т.д. + + +* Автоматическое документирование моделей данных в соответствии с JSON Schema (так как спецификация OpenAPI сама основана на JSON Schema). +* Разработан, придерживаясь этих стандартов, после тщательного их изучения. Эти стандарты изначально включены во фреймфорк, а не являются дополнительной надстройкой. +* Это также позволяет использовать автоматическую **генерацию клиентского кода** на многих языках. + +### Автоматически генерируемая документация + +Интерактивная документация для API и исследования пользовательских веб-интерфейсов. Поскольку этот фреймворк основан на OpenAPI, существует несколько вариантов документирования, 2 из которых включены по умолчанию. + +* Swagger UI, с интерактивным взаимодействием, вызывает и тестирует ваш API прямо из браузера. + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* Альтернативная документация API в ReDoc. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### Только современный Python + +Все эти возможности основаны на стандартных **аннотациях типов Python 3.6** (благодаря Pydantic). Не нужно изучать новый синтаксис. Только лишь стандартный современный Python. + +Если вам нужно освежить знания, как использовать аннотации типов в Python (даже если вы не используете FastAPI), выделите 2 минуты и просмотрите краткое руководство: [Введение в аннотации типов Python¶ +](python-types.md){.internal-link target=_blank}. + +Вы пишете на стандартном Python с аннотациями типов: + +```Python +from datetime import date + +from pydantic import BaseModel + +# Объявляем параметр user_id с типом `str` +# и получаем поддержку редактора внутри функции +def main(user_id: str): + return user_id + + +# Модель Pydantic +class User(BaseModel): + id: int + name: str + joined: date +``` + +Это можно использовать так: + +```Python +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +!!! Информация + `**second_user_data` означает: + + Передать ключи и значения словаря `second_user_data`, в качестве аргументов типа "ключ-значение", это эквивалентно: `User(id=4, name="Mary", joined="2018-11-30")` . + +### Поддержка редакторов (IDE) + +Весь фреймворк был продуман так, чтобы быть простым и интуитивно понятным в использовании, все решения были проверены на множестве редакторов еще до начала разработки, чтобы обеспечить наилучшие условия при написании кода. + +В опросе Python-разработчиков было выяснено, что наиболее часто используемой функцией редакторов, является "автодополнение". + +Вся структура **FastAPI** основана на удовлетворении этой возможности. Автодополнение работает везде. + +Вам редко нужно будет возвращаться к документации. + +Вот как ваш редактор может вам помочь: + +* в Visual Studio Code: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +* в PyCharm: + +![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) + +Вы будете получать автодополнение кода даже там, где вы считали это невозможным раньше. +Как пример, ключ `price` внутри тела JSON (который может быть вложенным), приходящего в запросе. + +Больше никаких неправильных имён ключей, метания по документации или прокручивания кода вверх и вниз, в попытках узнать - использовали вы ранее `username` или `user_name`. + +### Краткость +FastAPI имеет продуманные значения **по умолчанию** для всего, с произвольными настройками везде. Все параметры могут быть тонко подстроены так, чтобы делать то, что вам нужно и определять необходимый вам API. + +Но, по умолчанию, всё это **"и так работает"**. + +### Проверка значений + +* Проверка значений для большинства (или всех?) **типов данных** Python, включая: + * Объекты JSON (`dict`). + * Массивы JSON (`list`) с установленными типами элементов. + * Строковые (`str`) поля с ограничением минимальной и максимальной длины. + * Числа (`int`, `float`) с минимальными и максимальными значениями и т.п. + +* Проверка для более экзотических типов, таких как: + * URL. + * Email. + * UUID. + * ...и другие. + +Все проверки обрабатываются хорошо зарекомендовавшим себя и надежным **Pydantic**. + +### Безопасность и аутентификация + +Встроеные функции безопасности и аутентификации. Без каких-либо компромиссов с базами данных или моделями данных. + +Все схемы безопасности, определённые в OpenAPI, включая: + +* HTTP Basic. +* **OAuth2** (также с **токенами JWT**). Ознакомьтесь с руководством [OAuth2 с JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* Ключи API в: + * Заголовках. + * Параметрах запросов. + * Cookies и т.п. + +Вдобавок все функции безопасности от Starlette (включая **сессионные cookies**). + +Все инструменты и компоненты спроектированы для многократного использования и легко интегрируются с вашими системами, хранилищами данных, реляционными и NoSQL базами данных и т. д. + +### Внедрение зависимостей + +FastAPI включает в себя чрезвычайно простую в использовании, но чрезвычайно мощную систему Внедрения зависимостей. + +* Даже зависимости могут иметь зависимости, создавая иерархию или **"графы" зависимостей**. +* Всё **автоматически обрабатывается** фреймворком. +* Все зависимости могут запрашивать данные из запросов и **дополнять операции пути** ограничениями и автоматической документацией. +* **Автоматическая проверка** даже для параметров *операций пути*, определенных в зависимостях. +* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т.д. +* **Никаких компромиссов** с базами данных, интерфейсами и т.д. Но легкая интеграция со всеми ними. + +### Нет ограничений на "Плагины" + +Или, другими словами, нет сложностей с ними, импортируйте и используйте нужный вам код. + +Любая интеграция разработана настолько простой в использовании (с зависимостями), что вы можете создать "плагин" для своего приложения в пару строк кода, используя ту же структуру и синтаксис, что и для ваших *операций пути*. + +### Проверен + +* 100% покрытие тестами. +* 100% аннотирование типов в кодовой базе. +* Используется в реально работающих приложениях. + +## Основные свойства Starlette + +**FastAPI** основан на Starlette и полностью совместим с ним. Так что, любой дополнительный код Starlette, который у вас есть, будет также работать. + +На самом деле, `FastAPI` - это класс, унаследованный от `Starlette`. Таким образом, если вы уже знаете или используете Starlette, большая часть функционала будет работать так же. + +С **FastAPI** вы получаете все возможности **Starlette** (так как FastAPI это всего лишь Starlette на стероидах): + +* Серьёзно впечатляющая производительность. Это один из самых быстрых фреймворков на Python, наравне с приложениями использующими **NodeJS** или **Go**. +* Поддержка **WebSocket**. +* Фоновые задачи для процессов. +* События запуска и выключения. +* Тестовый клиент построен на библиотеке HTTPX. +* **CORS**, GZip, статические файлы, потоковые ответы. +* Поддержка **сессий и cookie**. +* 100% покрытие тестами. +* 100% аннотирование типов в кодовой базе. + +## Особенности и возможности Pydantic + +**FastAPI** основан на Pydantic и полностью совместим с ним. Так что, любой дополнительный код Pydantic, который у вас есть, будет также работать. + +Включая внешние библиотеки, также основанные на Pydantic, такие как: ORM'ы, ODM'ы для баз данных. + +Это также означает, что во многих случаях вы можете передавать тот же объект, который получили из запроса, **непосредственно в базу данных**, так как всё проверяется автоматически. + +И наоборот, во многих случаях вы можете просто передать объект, полученный из базы данных, **непосредственно клиенту**. + +С **FastAPI** вы получаете все возможности **Pydantic** (так как, FastAPI основан на Pydantic, для обработки данных): + +* **Никакой нервотрёпки** : + * Не нужно изучать новых схем в микроязыках. + * Если вы знаете аннотации типов в Python, вы знаете, как использовать Pydantic. +* Прекрасно сочетается с вашими **IDE/linter/мозгом**: + * Потому что структуры данных pydantic - это всего лишь экземпляры классов, определённых вами. Автодополнение, проверка кода, mypy и ваша интуиция - всё будет работать с вашими проверенными данными. +* **Быстродействие**: + * В тестовых замерах Pydantic быстрее, чем все другие проверенные библиотеки. +* Проверка **сложных структур**: + * Использование иерархических моделей Pydantic; `List`, `Dict` и т.п. из модуля `typing` (входит в стандартную библиотеку Python). + * Валидаторы позволяют четко и легко определять, проверять и документировать сложные схемы данных в виде JSON Schema. + * У вас могут быть глубоко **вложенные объекты JSON** и все они будут проверены и аннотированы. +* **Расширяемость**: + * Pydantic позволяет определять пользовательские типы данных или расширять проверку методами модели, с помощью проверочных декораторов. +* 100% покрытие тестами. diff --git a/docs/ru/docs/help-fastapi.md b/docs/ru/docs/help-fastapi.md new file mode 100644 index 0000000000..a69e37bd8c --- /dev/null +++ b/docs/ru/docs/help-fastapi.md @@ -0,0 +1,257 @@ +# Помочь FastAPI - Получить помощь + +Нравится ли Вам **FastAPI**? + +Хотели бы Вы помочь FastAPI, его пользователям и автору? + +Может быть у Вас возникли трудности с **FastAPI** и Вам нужна помощь? + +Есть несколько очень простых способов оказания помощи (иногда достаточно всего лишь одного или двух кликов). + +И также есть несколько способов получить помощь. + +## Подписаться на новостную рассылку + +Вы можете подписаться на редкую [новостную рассылку **FastAPI и его друзья**](/newsletter/){.internal-link target=_blank} и быть в курсе о: + +* Новостях о FastAPI и его друзьях 🚀 +* Руководствах 📝 +* Возможностях ✨ +* Исправлениях 🚨 +* Подсказках и хитростях ✅ + +## Подписаться на FastAPI в Twitter + +Подписаться на @fastapi в **Twitter** для получения наисвежайших новостей о **FastAPI**. 🐦 + +## Добавить **FastAPI** звезду на GitHub + +Вы можете добавить FastAPI "звезду" на GitHub (кликнуть на кнопку звезды в верхнем правом углу экрана): https://github.com/tiangolo/fastapi. ⭐️ + +Чем больше звёзд, тем легче другим пользователям найти нас и увидеть, что проект уже стал полезным для многих. + +## Отслеживать свежие выпуски в репозитории на GitHub + +Вы можете "отслеживать" FastAPI на GitHub (кликните по кнопке "watch" наверху справа): https://github.com/tiangolo/fastapi. 👀 + +Там же Вы можете указать в настройках - "Releases only". + +С такой настройкой Вы будете получать уведомления на вашу электронную почту каждый раз, когда появится новый релиз (новая версия) **FastAPI** с исправлениями ошибок и новыми возможностями. + +## Связаться с автором + +Можно связаться со мной (Себястьян Рамирез / `tiangolo`), автором FastAPI. + +Вы можете: + +* Подписаться на меня на **GitHub**. + * Посмотреть другие мои проекты с открытым кодом, которые могут быть полезны Вам. + * Подписавшись на меня Вы сможете получать уведомления, что я создал новый проект с открытым кодом,. +* Подписаться на меня в **Twitter** или в Mastodon. + * Поделиться со мной, как Вы используете FastAPI (я обожаю читать про это). + * Получать уведомления, когда я делаю объявления и представляю новые инструменты. + * Вы также можете подписаться на @fastapi в Twitter (это отдельный аккаунт). +* Подписаться на меня в **Linkedin**. + * Получать уведомления, когда я делаю объявления и представляю новые инструменты (правда чаще всего я использую Twitter 🤷‍♂). +* Читать, что я пишу (или подписаться на меня) в **Dev.to** или в **Medium**. + * Читать другие идеи, статьи и читать об инструментах созданных мной. + * Подпишитесь на меня, чтобы прочитать, когда я опубликую что-нибудь новое. + +## Оставить сообщение в Twitter о **FastAPI** + +Оставьте сообщение в Twitter о **FastAPI** и позвольте мне и другим узнать - почему он Вам нравится. 🎉 + +Я люблю узнавать о том, как **FastAPI** используется, что Вам понравилось в нём, в каких проектах/компаниях Вы используете его и т.п. + +## Оставить голос за FastAPI + +* Голосуйте за **FastAPI** в Slant. +* Голосуйте за **FastAPI** в AlternativeTo. +* Расскажите, как Вы используете **FastAPI** на StackShare. + +## Помочь другим с их проблемами на GitHub + +Вы можете посмотреть, какие проблемы испытывают другие люди и попытаться помочь им. Чаще всего это вопросы, на которые, весьма вероятно, Вы уже знаете ответ. 🤓 + +Если Вы будете много помогать людям с решением их проблем, Вы можете стать официальным [Экспертом FastAPI](fastapi-people.md#experts){.internal-link target=_blank}. 🎉 + +Только помните, самое важное при этом - доброта. Столкнувшись с проблемой, люди расстраиваются и часто задают вопросы не лучшим образом, но постарайтесь быть максимально доброжелательным. 🤗 + +Идея сообщества **FastAPI** в том, чтобы быть добродушным и гостеприимными. Не допускайте издевательств или неуважительного поведения по отношению к другим. Мы должны заботиться друг о друге. + +--- + +Как помочь другим с их проблемами: + +### Понять вопрос + +* Удостоверьтесь, что поняли **цель** и обстоятельства случая вопрошающего. + +* Затем проверьте, что вопрос (в подавляющем большинстве - это вопросы) Вам **ясен**. + +* Во многих случаях вопрос касается решения, которое пользователь придумал сам, но может быть и решение **получше**. Если Вы поймёте проблему и обстоятельства случая, то сможете предложить **альтернативное решение**. + +* Ежели вопрос Вам непонятен, запросите больше **деталей**. + +### Воспроизвести проблему + +В большинстве случаев есть что-то связанное с **исходным кодом** вопрошающего. + +И во многих случаях будет предоставлен только фрагмент этого кода, которого недостаточно для **воспроизведения проблемы**. + +* Попросите предоставить минимальный воспроизводимый пример, который можно **скопировать** и запустить локально дабы увидеть такую же ошибку, или поведение, или лучше понять обстоятельства случая. + +* Если на Вас нахлынуло великодушие, то можете попытаться **создать похожий пример** самостоятельно, основываясь только на описании проблемы. Но имейте в виду, что это может занять много времени и, возможно, стоит сначала позадавать вопросы для прояснения проблемы. + +### Предложить решение + +* После того как Вы поняли вопрос, Вы можете дать **ответ**. + +* Следует понять **основную проблему и обстоятельства случая**, потому что может быть решение лучше, чем то, которое пытались реализовать. + +### Попросить закрыть проблему + +Если Вам ответили, высоки шансы, что Вам удалось решить проблему, поздравляю, **Вы - герой**! 🦸 + +* В таком случае, если вопрос решён, попросите **закрыть проблему**. + +## Отслеживать репозиторий на GitHub + +Вы можете "отслеживать" FastAPI на GitHub (кликните по кнопке "watch" наверху справа): https://github.com/tiangolo/fastapi. 👀 + +Если Вы выберете "Watching" вместо "Releases only", то будете получать уведомления когда кто-либо попросит о помощи с решением его проблемы. + +Тогда Вы можете попробовать решить эту проблему. + +## Запросить помощь с решением проблемы + +Вы можете создать новый запрос с просьбой о помощи в репозитории на GitHub, например: + +* Задать **вопрос** или попросить помощи в решении **проблемы**. +* Предложить новое **улучшение**. + +**Заметка**: Если Вы создаёте подобные запросы, то я попрошу Вас также оказывать аналогичную помощь другим. 😉 + +## Проверять пул-реквесты + +Вы можете помочь мне проверять пул-реквесты других участников. + +И повторюсь, постарайтесь быть доброжелательным. 🤗 + +--- + +О том, что нужно иметь в виду при проверке пул-реквестов: + +### Понять проблему + +* Во-первых, убедитесь, что **поняли проблему**, которую пул-реквест пытается решить. Для этого может потребоваться продолжительное обсуждение. + +* Также есть вероятность, что пул-реквест не актуален, так как проблему можно решить **другим путём**. В таком случае Вы можете указать на этот факт. + +### Не переживайте о стиле + +* Не стоит слишком беспокоиться о таких вещах, как стиль сообщений в коммитах или количество коммитов. При слиянии пул-реквеста с основной веткой, я буду сжимать и настраивать всё вручную. + +* Также не беспокойтесь о правилах стиля, для проверки сего есть автоматизированные инструменты. + +И если всё же потребуется какой-то другой стиль, я попрошу Вас об этом напрямую или добавлю сам коммиты с необходимыми изменениями. + +### Проверить код + +* Проверьте и прочитайте код, посмотрите, какой он имеет смысл, **запустите его локально** и посмотрите, действительно ли он решает поставленную задачу. + +* Затем, используя **комментарий**, сообщите, что Вы сделали проверку, тогда я буду знать, что Вы действительно проверили код. + +!!! Информация + К сожалению, я не могу так просто доверять пул-реквестам, у которых уже есть несколько одобрений. + + Бывали случаи, что пул-реквесты имели 3, 5 или больше одобрений, вероятно из-за привлекательного описания, но когда я проверял эти пул-реквесты, они оказывались сломаны, содержали ошибки или вовсе не решали проблему, которую, как они утверждали, должны были решить. 😅 + + Потому это действительно важно - проверять и запускать код, и комментарием уведомлять меня, что Вы проделали эти действия. 🤓 + +* Если Вы считаете, что пул-реквест можно упростить, то можете попросить об этом, но не нужно быть слишком придирчивым, может быть много субъективных точек зрения (и у меня тоже будет своя 🙈), поэтому будет лучше, если Вы сосредоточитесь на фундаментальных вещах. + +### Тестировать + +* Помогите мне проверить, что у пул-реквеста есть **тесты**. + +* Проверьте, что тесты **падали** до пул-реквеста. 🚨 + +* Затем проверьте, что тесты **не валятся** после пул-реквеста. ✅ + +* Многие пул-реквесты не имеют тестов, Вы можете **напомнить** о необходимости добавления тестов или даже **предложить** какие-либо свои тесты. Это одна из тех вещей, которые отнимают много времени и Вы можете помочь с этим. + +* Затем добавьте комментарий, что Вы испробовали в ходе проверки. Таким образом я буду знать, как Вы произвели проверку. 🤓 + +## Создать пул-реквест + +Вы можете [сделать вклад](contributing.md){.internal-link target=_blank} в код фреймворка используя пул-реквесты, например: + +* Исправить опечатку, которую Вы нашли в документации. +* Поделиться статьёй, видео или подкастом о FastAPI, которые Вы создали или нашли изменив этот файл. + * Убедитесь, что Вы добавили свою ссылку в начало соответствующего раздела. +* Помочь с [переводом документации](contributing.md#translations){.internal-link target=_blank} на Ваш язык. + * Вы также можете проверять переводы сделанные другими. +* Предложить новые разделы документации. +* Исправить существующуе проблемы/баги. + * Убедитесь, что добавили тесты. +* Добавить новую возможность. + * Убедитесь, что добавили тесты. + * Убедитесь, что добавили документацию, если она необходима. + +## Помочь поддерживать FastAPI + +Помогите мне поддерживать **FastAPI**! 🤓 + +Предстоит ещё много работы и, по большей части, **ВЫ** можете её сделать. + +Основные задачи, которые Вы можете выполнить прямо сейчас: + +* [Помочь другим с их проблемами на GitHub](#help-others-with-issues-in-github){.internal-link target=_blank} (смотрите вышестоящую секцию). +* [Проверить пул-реквесты](#review-pull-requests){.internal-link target=_blank} (смотрите вышестоящую секцию). + +Эти две задачи **отнимают больше всего времени**. Это основная работа по поддержке FastAPI. + +Если Вы можете помочь мне с этим, **Вы помогаете поддерживать FastAPI** и следить за тем, чтобы он продолжал **развиваться быстрее и лучше**. 🚀 + +## Подключиться к чату + +Подключайтесь к 👥 чату в Discord 👥 и общайтесь с другими участниками сообщества FastAPI. + +!!! Подсказка + Вопросы по проблемам с фреймворком лучше задавать в GitHub issues, так больше шансов, что Вы получите помощь от [Экспертов FastAPI](fastapi-people.md#experts){.internal-link target=_blank}. + + Используйте этот чат только для бесед на отвлечённые темы. + +Существует также чат в Gitter, но поскольку в нем нет каналов и расширенных функций, общение в нём сложнее, потому рекомендуемой системой является Discord. + +### Не использовать чаты для вопросов + +Имейте в виду, что чаты позволяют больше "свободного общения", потому там легко задавать вопросы, которые слишком общие и на которые труднее ответить, так что Вы можете не получить нужные Вам ответы. + +В разделе "проблемы" на GitHub, есть шаблон, который поможет Вам написать вопрос правильно, чтобы Вам было легче получить хороший ответ или даже решить проблему самостоятельно, прежде чем Вы зададите вопрос. В GitHub я могу быть уверен, что всегда отвечаю на всё, даже если это займет какое-то время. И я не могу сделать то же самое в чатах. 😅 + +Кроме того, общение в чатах не так легкодоступно для поиска, как в GitHub, потому вопросы и ответы могут потеряться среди другого общения. И только проблемы решаемые на GitHub учитываются в получении лычки [Эксперт FastAPI](fastapi-people.md#experts){.internal-link target=_blank}, так что весьма вероятно, что Вы получите больше внимания на GitHub. + +С другой стороны, в чатах тысячи пользователей, а значит есть большие шансы в любое время найти там кого-то, с кем можно поговорить. 😄 + +## Спонсировать автора + +Вы также можете оказать мне финансовую поддержку посредством спонсорства через GitHub. + +Там можно просто купить мне кофе ☕️ в знак благодарности. 😄 + +А ещё Вы можете стать Серебряным или Золотым спонсором для FastAPI. 🏅🎉 + +## Спонсировать инструменты, на которых зиждется мощь FastAPI + +Как Вы могли заметить в документации, FastAPI опирается на плечи титанов: Starlette и Pydantic. + +Им тоже можно оказать спонсорскую поддержку: + +* Samuel Colvin (Pydantic) +* Encode (Starlette, Uvicorn) + +--- + +Благодарствую! 🚀 diff --git a/docs/ru/docs/history-design-future.md b/docs/ru/docs/history-design-future.md new file mode 100644 index 0000000000..2a5e428b1b --- /dev/null +++ b/docs/ru/docs/history-design-future.md @@ -0,0 +1,77 @@ +# История создания и дальнейшее развитие + +Однажды, один из пользователей **FastAPI** задал вопрос: + +> Какова история этого проекта? Создаётся впечатление, что он явился из ниоткуда и завоевал мир за несколько недель [...] + +Что ж, вот небольшая часть истории проекта. + +## Альтернативы + +В течение нескольких лет я, возглавляя различные команды разработчиков, создавал довольно сложные API для машинного обучения, распределённых систем, асинхронных задач, баз данных NoSQL и т.д. + +В рамках работы над этими проектами я исследовал, проверял и использовал многие фреймворки. + +Во многом история **FastAPI** - история его предшественников. + +Как написано в разделе [Альтернативы](alternatives.md){.internal-link target=_blank}: + +
+ +**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/index.md b/docs/ru/docs/index.md index a1d3022762..30c32e0463 100644 --- a/docs/ru/docs/index.md +++ b/docs/ru/docs/index.md @@ -1,50 +1,48 @@ - -{!../../../docs/missing-translation.md!} - -

FastAPI

- FastAPI framework, high performance, easy to learn, fast to code, ready for production + Готовый к внедрению высокопроизводительный фреймворк, простой в изучении и разработке.

- - Build Status + + Test - Coverage + Coverage - Package version + Package version + + + Supported Python versions

--- -**Documentation**: https://fastapi.tiangolo.com +**Документация**: https://fastapi.tiangolo.com -**Source Code**: https://github.com/tiangolo/fastapi +**Исходный код**: https://github.com/tiangolo/fastapi --- -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. +FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API используя Python 3.6+, в основе которого лежит стандартная аннотация типов Python. -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). +* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых фреймворков Python](#_10). +* **Быстрота разработки**: Увеличьте скорость разработки примерно на 200–300%. * +* **Меньше ошибок**: Сократите примерно на 40% количество ошибок, вызванных человеком (разработчиком). * +* **Интуитивно понятный**: Отличная поддержка редактора. Автозавершение везде. Меньше времени на отладку. +* **Лёгкость**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации. +* **Краткость**: Сведите к минимуму дублирование кода. Каждый объявленный параметр - определяет несколько функций. Меньше ошибок. +* **Надежность**: Получите готовый к работе код. С автоматической интерактивной документацией. +* **На основе стандартов**: Основан на открытых стандартах API и полностью совместим с ними: OpenAPI (ранее известном как Swagger) и JSON Schema. -* **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 +## Спонсоры @@ -59,66 +57,66 @@ The key features are: -Other sponsors +Другие спонсоры -## Opinions +## Отзывы -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" +"_В последнее время я много где использую **FastAPI**. [...] На самом деле я планирую использовать его для всех **сервисов машинного обучения моей команды в Microsoft**. Некоторые из них интегрируются в основной продукт **Windows**, а некоторые — в продукты **Office**._"
Kabir Khan - Microsoft (ref)
--- -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" +"_Мы использовали библиотеку **FastAPI** для создания сервера **REST**, к которому можно делать запросы для получения **прогнозов**. [для Ludwig]_"
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
--- -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" +"_**Netflix** рада объявить о выпуске опенсорсного фреймворка для оркестровки **антикризисного управления**: **Dispatch**! [создана с помощью **FastAPI**]_"
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
--- -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" +"_Я в полном восторге от **FastAPI**. Это так весело!_"
Brian Okken - Python Bytes podcast host (ref)
--- -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" +"_Честно говоря, то, что вы создали, выглядит очень солидно и отполировано. Во многих смыслах я хотел, чтобы **Hug** был именно таким — это действительно вдохновляет, когда кто-то создаёт такое._"
Timothy Crosley - Hug creator (ref)
--- -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" +"_Если вы хотите изучить какой-нибудь **современный фреймворк** для создания REST API, ознакомьтесь с **FastAPI** [...] Он быстрый, лёгкий и простой в изучении [...]_" -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" +"_Мы перешли на **FastAPI** для наших **API** [...] Я думаю, вам тоже понравится [...]_"
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
--- -## **Typer**, the FastAPI of CLIs +## **Typer**, интерфейс командной строки для FastAPI -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. +Если вы создаете приложение CLI для использования в терминале вместо веб-API, ознакомьтесь с **Typer**. -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 +**Typer** — младший брат FastAPI. И он предназначен для использования в качестве **интерфейса командной строки для FastAPI**. ⌨️ 🚀 -## Requirements +## Зависимости -Python 3.6+ +Python 3.7+ -FastAPI stands on the shoulders of giants: +FastAPI стоит на плечах гигантов: -* Starlette for the web parts. -* Pydantic for the data parts. +* Starlette для части связанной с вебом. +* Pydantic для части связанной с данными. -## Installation +## Установка
@@ -130,26 +128,26 @@ $ pip install fastapi
-You will also need an ASGI server, for production such as Uvicorn or Hypercorn. +Вам также понадобится сервер ASGI для производства, такой как Uvicorn или Hypercorn.
```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ```
-## Example +## Пример -### Create it +### Создание -* Create a file `main.py` with: +* Создайте файл `main.py` со следующим содержимым: ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -162,17 +160,17 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ```
-Or use async def... +Или используйте async def... -If your code uses `async` / `await`, use `async def`: +Если ваш код использует `async` / `await`, используйте `async def`: ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -185,19 +183,19 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -**Note**: +**Примечание**: -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. +Если вы не знаете, проверьте раздел _"Торопитесь?"_ в документации об `async` и `await`.
-### Run it +### Запуск -Run the server with: +Запустите сервер с помощью:
@@ -214,57 +212,57 @@ INFO: Application startup complete.
-About the command uvicorn main:app --reload... +О команде uvicorn main:app --reload... -The command `uvicorn main:app` refers to: +Команда `uvicorn main:app` относится к: -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. +* `main`: файл `main.py` (модуль Python). +* `app`: объект, созданный внутри `main.py` с помощью строки `app = FastAPI()`. +* `--reload`: перезапуск сервера после изменения кода. Делайте это только во время разработки.
-### Check it +### Проверка -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. +Откройте браузер на http://127.0.0.1:8000/items/5?q=somequery. -You will see the JSON response as: +Вы увидите следующий JSON ответ: ```JSON {"item_id": 5, "q": "somequery"} ``` -You already created an API that: +Вы уже создали API, который: -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. +* Получает HTTP-запросы по _путям_ `/` и `/items/{item_id}`. +* И первый и второй _путь_ используют `GET` операции (также известные как HTTP _методы_). +* _путь_ `/items/{item_id}` имеет _параметр пути_ `item_id`, который должен быть `int`. +* _путь_ `/items/{item_id}` имеет необязательный `str` _параметр запроса_ `q`. -### Interactive API docs +### Интерактивная документация по API -Now go to http://127.0.0.1:8000/docs. +Перейдите на http://127.0.0.1:8000/docs. -You will see the automatic interactive API documentation (provided by Swagger UI): +Вы увидите автоматическую интерактивную документацию API (предоставленную Swagger UI): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Alternative API docs +### Альтернативная документация по API -And now, go to http://127.0.0.1:8000/redoc. +А теперь перейдите на http://127.0.0.1:8000/redoc. -You will see the alternative automatic documentation (provided by ReDoc): +Вы увидите альтернативную автоматическую документацию (предоставленную ReDoc): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Example upgrade +## Пример обновления -Now modify the file `main.py` to receive a body from a `PUT` request. +Теперь измените файл `main.py`, чтобы получить тело ответа из `PUT` запроса. -Declare the body using standard Python types, thanks to Pydantic. +Объявите тело, используя стандартную типизацию Python, спасибо Pydantic. ```Python hl_lines="4 9-12 25-27" -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -275,7 +273,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Optional[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -284,7 +282,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} @@ -293,174 +291,172 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). +Сервер должен перезагрузиться автоматически (потому что вы добавили `--reload` к команде `uvicorn` выше). -### Interactive API docs upgrade +### Интерактивное обновление документации API -Now go to http://127.0.0.1:8000/docs. +Перейдите на http://127.0.0.1:8000/docs. -* The interactive API documentation will be automatically updated, including the new body: +* Интерактивная документация API будет автоматически обновляться, включая новое тело: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: +* Нажмите на кнопку "Try it out", это позволит вам заполнить параметры и напрямую взаимодействовать с API: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: +* Затем нажмите кнопку "Execute", пользовательский интерфейс свяжется с вашим API, отправит параметры, получит результаты и отобразит их на экране: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### Alternative API docs upgrade +### Альтернативное обновление документации API -And now, go to http://127.0.0.1:8000/redoc. +А теперь перейдите на http://127.0.0.1:8000/redoc. -* The alternative documentation will also reflect the new query parameter and body: +* Альтернативная документация также будет отражать новый параметр и тело запроса: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Recap +### Подведём итоги -In summary, you declare **once** the types of parameters, body, etc. as function parameters. +Таким образом, вы объявляете **один раз** типы параметров, тело и т. д. в качестве параметров функции. -You do that with standard modern Python types. +Вы делаете это испльзуя стандартную современную типизацию Python. -You don't have to learn a new syntax, the methods or classes of a specific library, etc. +Вам не нужно изучать новый синтаксис, методы или классы конкретной библиотеки и т. д. -Just standard **Python 3.6+**. +Только стандартный **Python 3.6+**. -For example, for an `int`: +Например, для `int`: ```Python item_id: int ``` -or for a more complex `Item` model: +или для более сложной модели `Item`: ```Python item: Item ``` -...and with that single declaration you get: +... и с этим единственным объявлением вы получаете: -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: +* Поддержка редактора, в том числе: + * Автозавершение. + * Проверка типов. +* Валидация данных: + * Автоматические и четкие ошибки, когда данные недействительны. + * Проверка даже для глубоко вложенных объектов JSON. +* Преобразование входных данных: поступающие из сети в объекты Python с соблюдением типов. Чтение из: * JSON. - * Path parameters. - * Query parameters. + * Параметров пути. + * Параметров запроса. * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: + * Заголовков. + * Форм. + * Файлов. +* Преобразование выходных данных: преобразование объектов Python в данные передаваемые по сети интернет (такие как JSON): + * Преобразование типов Python (`str`, `int`, `float`, `bool`, `list`, и т.д.). + * Объекты `datetime`. + * Объекты `UUID`. + * Модели баз данных. + * ...и многое другое. +* Автоматическая интерактивная документация по API, включая 2 альтернативных пользовательских интерфейса: * Swagger UI. * ReDoc. --- -Coming back to the previous code example, **FastAPI** will: +Возвращаясь к предыдущему примеру кода, **FastAPI** будет: -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. +* Проверять наличие `item_id` в пути для запросов `GET` и `PUT`. +* Проверять, что `item_id` имеет тип `int` для запросов `GET` и `PUT`. + * Если это не так, клиент увидит полезную чёткую ошибку. +* Проверять, есть ли необязательный параметр запроса с именем `q` (например, `http://127.0.0.1:8000/items/foo?q=somequery`) для `GET` запросов. + * Поскольку параметр `q` объявлен с `= None`, он является необязательным. + * Без `None` он был бы необходим (как тело в случае с `PUT`). +* Для `PUT` запросов к `/items/{item_id}` читать тело как JSON: + * Проверять, что у него есть обязательный атрибут `name`, который должен быть `str`. + * Проверять, что у него есть обязательный атрибут `price`, который должен быть `float`. + * Проверять, что у него есть необязательный атрибут `is_offer`, который должен быть `bool`, если он присутствует. + * Все это также будет работать для глубоко вложенных объектов JSON. +* Преобразовывать из и в JSON автоматически. +* Документировать с помощью OpenAPI все, что может быть использовано: + * Системы интерактивной документации. + * Системы автоматической генерации клиентского кода для многих языков. +* Обеспечит 2 интерактивных веб-интерфейса документации напрямую. --- -We just scratched the surface, but you already get the idea of how it all works. +Мы только немного копнули поверхность, но вы уже поняли, как все это работает. -Try changing the line with: +Попробуйте изменить строку с помощью: ```Python return {"item_name": item.name, "item_id": item_id} ``` -...from: +...из: ```Python ... "item_name": item.name ... ``` -...to: +...в: ```Python ... "item_price": item.price ... ``` -...and see how your editor will auto-complete the attributes and know their types: +... и посмотрите, как ваш редактор будет автоматически заполнять атрибуты и узнавать их типы: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -For a more complete example including more features, see the Tutorial - User Guide. +Более полный пример с дополнительными функциями см. в Учебное руководство - Руководство пользователя. -**Spoiler alert**: the tutorial - user guide includes: +**Осторожно, спойлер**: руководство пользователя включает в себя: -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on `requests` and `pytest` +* Объявление **параметров** из других мест, таких как: **заголовки**, **cookies**, **поля формы** и **файлы**. +* Как установить **ограничительные проверки** такие как `maximum_length` или `regex`. +* Очень мощная и простая в использовании система **внедрения зависимостей**. +* Безопасность и аутентификация, включая поддержку **OAuth2** с **токенами JWT** и **HTTP Basic** аутентификацию. +* Более продвинутые (но столь же простые) методы объявления **глубоко вложенных моделей JSON** (спасибо Pydantic). +* **GraphQL** интеграция с Strawberry и другими библиотеками. +* Множество дополнительных функций (благодаря Starlette), таких как: + * **Веб-сокеты** + * очень простые тесты на основе HTTPX и `pytest` * **CORS** - * **Cookie Sessions** - * ...and more. + * **Cookie сеансы(сессии)** + * ...и многое другое. -## Performance +## Производительность -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) +Независимые тесты TechEmpower показывают приложения **FastAPI**, работающие под управлением Uvicorn, как один из самых быстрых доступных фреймворков Python, уступающий только самим Starlette и Uvicorn (используемых внутри FastAPI). (*) -To understand more about it, see the section Benchmarks. +Чтобы узнать больше об этом, см. раздел Тесты производительности. -## Optional Dependencies +## Необязательные зависимости -Used by Pydantic: +Используется Pydantic: -* ujson - for faster JSON "parsing". -* email_validator - for email validation. +* email_validator - для проверки электронной почты. -Used by Starlette: +Используется Starlette: -* requests - 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). -* graphene - Required for `GraphQLApp` support. -* ujson - Required if you want to use `UJSONResponse`. +* HTTPX - Обязательно, если вы хотите использовать `TestClient`. +* jinja2 - Обязательно, если вы хотите использовать конфигурацию шаблона по умолчанию. +* python-multipart - Обязательно, если вы хотите поддерживать форму "парсинга" с помощью `request.form()`. +* itsdangerous - Обязательно, для поддержки `SessionMiddleware`. +* pyyaml - Обязательно, для поддержки `SchemaGenerator` Starlette (возможно, вам это не нужно с FastAPI). +* ujson - Обязательно, если вы хотите использовать `UJSONResponse`. -Used by FastAPI / Starlette: +Используется FastAPI / Starlette: -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. +* uvicorn - сервер, который загружает и обслуживает ваше приложение. +* orjson - Обязательно, если вы хотите использовать `ORJSONResponse`. -You can install all of these with `pip install fastapi[all]`. +Вы можете установить все это с помощью `pip install "fastapi[all]"`. -## License +## Лицензия -This project is licensed under the terms of the MIT license. +Этот проект распространяется на условиях лицензии MIT. diff --git a/docs/ru/docs/project-generation.md b/docs/ru/docs/project-generation.md new file mode 100644 index 0000000000..76253d6f2f --- /dev/null +++ b/docs/ru/docs/project-generation.md @@ -0,0 +1,84 @@ +# Генераторы проектов - Шаблоны + +Чтобы начать работу быстрее, Вы можете использовать "генераторы проектов", в которые включены множество начальных настроек для функций безопасности, баз данных и некоторые эндпоинты API. + +В генераторе проектов всегда будут предустановлены какие-то настройки, которые Вам следует обновить и подогнать под свои нужды, но это может быть хорошей отправной точкой для Вашего проекта. + +## Full Stack FastAPI PostgreSQL + +GitHub: https://github.com/tiangolo/full-stack-fastapi-postgresql + +### Full Stack FastAPI PostgreSQL - Особенности + +* Полностью интегрирован с **Docker** (основан на Docker). +* Развёртывается в режиме Docker Swarm. +* Интегрирован с **Docker Compose** и оптимизирован для локальной разработки. +* **Готовый к реальной работе** веб-сервер Python использующий Uvicorn и Gunicorn. +* Бэкенд построен на фреймворке **FastAPI**: + * **Быстрый**: Высокопроизводительный, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). + * **Интуитивно понятный**: Отличная поддержка редактора. Автодополнение кода везде. Меньше времени на отладку. + * **Простой**: Разработан так, чтоб быть простым в использовании и изучении. Меньше времени на чтение документации. + * **Лаконичный**: Минимизировано повторение кода. Каждый объявленный параметр определяет несколько функций. + * **Надёжный**: Получите готовый к работе код. С автоматической интерактивной документацией. + * **Стандартизированный**: Основан на открытых стандартах API (OpenAPI и JSON Schema) и полностью совместим с ними. + * **Множество других возможностей** включая автоматическую проверку и сериализацию данных, интерактивную документацию, аутентификацию с помощью OAuth2 JWT-токенов и т.д. +* **Безопасное хранение паролей**, которые хэшируются по умолчанию. +* Аутентификация посредством **JWT-токенов**. +* Модели **SQLAlchemy** (независящие от расширений Flask, а значит могут быть непосредственно использованы процессами Celery). +* Базовая модель пользователя (измените или удалите её по необходимости). +* **Alembic** для организации миграций. +* **CORS** (Совместное использование ресурсов из разных источников). +* **Celery**, процессы которого могут выборочно импортировать и использовать модели и код из остальной части бэкенда. +* Тесты, на основе **Pytest**, интегрированные в Docker, чтобы Вы могли полностью проверить Ваше API, независимо от базы данных. Так как тесты запускаются в Docker, для них может создаваться новое хранилище данных каждый раз (Вы можете, по своему желанию, использовать ElasticSearch, MongoDB, CouchDB или другую СУБД, только лишь для проверки - будет ли Ваше API работать с этим хранилищем). +* Простая интеграция Python с **Jupyter Kernels** для разработки удалённо или в Docker с расширениями похожими на Atom Hydrogen или Visual Studio Code Jupyter. +* Фронтенд построен на фреймворке **Vue**: + * Сгенерирован с помощью Vue CLI. + * Поддерживает **аутентификацию с помощью JWT-токенов**. + * Страница логина. + * Перенаправление на страницу главной панели мониторинга после логина. + * Главная страница мониторинга с возможностью создания и изменения пользователей. + * Пользователь может изменять свои данные. + * **Vuex**. + * **Vue-router**. + * **Vuetify** для конструирования красивых компонентов страниц. + * **TypeScript**. + * Сервер Docker основан на **Nginx** (настроен для удобной работы с Vue-router). + * Многоступенчатая сборка Docker, то есть Вам не нужно сохранять или коммитить скомпилированный код. + * Тесты фронтенда запускаются во время сборки (можно отключить). + * Сделан настолько модульно, насколько возможно, поэтому работает "из коробки", но Вы можете повторно сгенерировать фронтенд с помощью Vue CLI или создать то, что Вам нужно и повторно использовать то, что захотите. +* **PGAdmin** для СУБД PostgreSQL, которые легко можно заменить на PHPMyAdmin и MySQL. +* **Flower** для отслеживания работы Celery. +* Балансировка нагрузки между фронтендом и бэкендом с помощью **Traefik**, а значит, Вы можете расположить их на одном домене, разделив url-пути, так как они обслуживаются разными контейнерами. +* Интеграция с Traefik включает автоматическую генерацию сертификатов Let's Encrypt для поддержки протокола **HTTPS**. +* GitLab **CI** (непрерывная интеграция), которая включает тестирование фронтенда и бэкенда. + +## Full Stack FastAPI Couchbase + +GitHub: https://github.com/tiangolo/full-stack-fastapi-couchbase + +⚠️ **ПРЕДУПРЕЖДЕНИЕ** ⚠️ + +Если Вы начинаете новый проект, ознакомьтесь с представленными здесь альтернативами. + +Например, генератор проектов Full Stack FastAPI PostgreSQL может быть более подходящей альтернативой, так как он активно поддерживается и используется. И он включает в себя все новые возможности и улучшения. + +Но никто не запрещает Вам использовать генератор с СУБД Couchbase, возможно, он всё ещё работает нормально. Или у Вас уже есть проект, созданный с помощью этого генератора ранее, и Вы, вероятно, уже обновили его в соответствии со своими потребностями. + +Вы можете прочитать о нём больше в документации соответствующего репозитория. + +## Full Stack FastAPI MongoDB + +...может быть когда-нибудь появится, в зависимости от наличия у меня свободного времени и прочих факторов. 😅 🎉 + +## Модели машинного обучения на основе spaCy и FastAPI + +GitHub: https://github.com/microsoft/cookiecutter-spacy-fastapi + +### Модели машинного обучения на основе spaCy и FastAPI - Особенности + +* Интеграция с моделями **spaCy** NER. +* Встроенный формат запросов к **когнитивному поиску Azure**. +* **Готовый к реальной работе** веб-сервер Python использующий Uvicorn и Gunicorn. +* Встроенное развёртывание на основе **Azure DevOps** Kubernetes (AKS) CI/CD. +* **Многоязычность**. Лёгкий выбор одного из встроенных в spaCy языков во время настройки проекта. +* **Легко подключить** модели из других фреймворков (Pytorch, Tensorflow) не ограничиваясь spaCy. diff --git a/docs/ru/docs/tutorial/background-tasks.md b/docs/ru/docs/tutorial/background-tasks.md new file mode 100644 index 0000000000..81efda786a --- /dev/null +++ b/docs/ru/docs/tutorial/background-tasks.md @@ -0,0 +1,102 @@ +# Фоновые задачи + +Вы можете создавать фоновые задачи, которые будут выполнятся *после* возвращения ответа сервером. + +Это может быть полезно для функций, которые должны выполниться после получения запроса, но ожидание их выполнения необязательно для пользователя. + +К примеру: + +* Отправка писем на почту после выполнения каких-либо действий: + * Т.к. соединение с почтовым сервером и отправка письма идут достаточно "долго" (несколько секунд), вы можете отправить ответ пользователю, а отправку письма выполнить в фоне. +* Обработка данных: + * К примеру, если вы получаете файл, который должен пройти через медленный процесс, вы можете отправить ответ "Accepted" (HTTP 202) и отправить работу с файлом в фон. + +## Использование класса `BackgroundTasks` + +Сначала импортируйте `BackgroundTasks`, потом добавьте в функцию параметр с типом `BackgroundTasks`: + +```Python hl_lines="1 13" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +**FastAPI** создаст объект класса `BackgroundTasks` для вас и запишет его в параметр. + +## Создание функции для фоновой задачи + +Создайте функцию, которую хотите запустить в фоне. + +Это совершенно обычная функция, которая может принимать параметры. + +Она может быть как асинхронной `async def`, так и обычной `def` функцией, **FastAPI** знает, как правильно ее выполнить. + +В нашем примере фоновая задача будет вести запись в файл (симулируя отправку письма). + +Так как операция записи не использует `async` и `await`, мы определим ее как обычную `def`: + +```Python hl_lines="6-9" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +## Добавление фоновой задачи + +Внутри функции вызовите метод `.add_task()` у объекта *background tasks* и передайте ему функцию, которую хотите выполнить в фоне: + +```Python hl_lines="14" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +`.add_task()` принимает следующие аргументы: + +* Функцию, которая будет выполнена в фоне (`write_notification`). Обратите внимание, что передается объект функции, без скобок. +* Любое упорядоченное количество аргументов, которые принимает функция (`email`). +* Любое количество именованных аргументов, которые принимает функция (`message="some notification"`). + +## Встраивание зависимостей + +Класс `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете определить `BackgroundTasks` на разных уровнях: как параметр функции, как завимость, как подзависимость и так далее. + +**FastAPI** знает, что нужно сделать в каждом случае и как переиспользовать тот же объект `BackgroundTasks`, так чтобы все фоновые задачи собрались и запустились вместе в фоне: + +=== "Python 3.10+" + + ```Python hl_lines="11 13 20 23" + {!> ../../../docs_src/background_tasks/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="13 15 22 25" + {!> ../../../docs_src/background_tasks/tutorial002.py!} + ``` + +В этом примере сообщения будут записаны в `log.txt` *после* того, как ответ сервера был отправлен. + +Если бы в запросе была очередь `q`, она бы первой записалась в `log.txt` фоновой задачей (потому что вызывается в зависимости `get_query`). + +После другая фоновая задача, которая была сгенерирована в функции, запишет сообщение из параметра `email`. + +## Технические детали + +Класс `BackgroundTasks` основан на `starlette.background`. + +Он интегрирован в FastAPI, так что вы можете импортировать его прямо из `fastapi` и избежать случайного импорта `BackgroundTask` (без `s` на конце) из `starlette.background`. + +При использовании `BackgroundTasks` (а не `BackgroundTask`), вам достаточно только определить параметр функции с типом `BackgroundTasks` и **FastAPI** сделает все за вас, также как при использовании объекта `Request`. + +Вы все равно можете использовать `BackgroundTask` из `starlette` в FastAPI, но вам придется самостоятельно создавать объект фоновой задачи и вручную обработать `Response` внутри него. + +Вы можете подробнее изучить его в Официальной документации Starlette для BackgroundTasks. + +## Предостережение + +Если вам нужно выполнить тяжелые вычисления в фоне, которым необязательно быть запущенными в одном процессе с приложением **FastAPI** (к примеру, вам не нужны обрабатываемые переменные или вы не хотите делиться памятью процесса и т.д.), вы можете использовать более серьезные инструменты, такие как Celery. + +Их тяжелее настраивать, также им нужен брокер сообщений наподобие RabbitMQ или Redis, но зато они позволяют вам запускать фоновые задачи в нескольких процессах и даже на нескольких серверах. + +Для примера, посмотрите [Project Generators](../project-generation.md){.internal-link target=_blank}, там есть проект с уже настроенным Celery. + +Но если вам нужен доступ к общим переменным и объектам вашего **FastAPI** приложения или вам нужно выполнять простые фоновые задачи (наподобие отправки письма из примера) вы можете просто использовать `BackgroundTasks`. + +## Резюме + +Для создания фоновых задач вам необходимо импортировать `BackgroundTasks` и добавить его в функцию, как параметр с типом `BackgroundTasks`. diff --git a/docs/ru/docs/tutorial/body-fields.md b/docs/ru/docs/tutorial/body-fields.md new file mode 100644 index 0000000000..674b8bde47 --- /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/body-multiple-params.md b/docs/ru/docs/tutorial/body-multiple-params.md new file mode 100644 index 0000000000..a20457092b --- /dev/null +++ b/docs/ru/docs/tutorial/body-multiple-params.md @@ -0,0 +1,309 @@ +# Body - Множество параметров + +Теперь, когда мы увидели, как использовать `Path` и `Query` параметры, давайте рассмотрим более продвинутые примеры обьявления тела запроса. + +## Обьединение `Path`, `Query` и параметров тела запроса + +Во-первых, конечно, вы можете объединять параметры `Path`, `Query` и объявления тела запроса в своих функциях обработки, **FastAPI** автоматически определит, что с ними нужно делать. + +Вы также можете объявить параметры тела запроса как необязательные, установив значение по умолчанию, равное `None`: + +=== "Python 3.10+" + + ```Python hl_lines="18-20" + {!> ../../../docs_src/body_multiple_params/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="18-20" + {!> ../../../docs_src/body_multiple_params/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="19-21" + {!> ../../../docs_src/body_multiple_params/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! Заметка + Рекомендуется использовать `Annotated` версию, если это возможно. + + ```Python hl_lines="17-19" + {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! Заметка + Рекомендуется использовать версию с `Annotated`, если это возможно. + + ```Python hl_lines="19-21" + {!> ../../../docs_src/body_multiple_params/tutorial001.py!} + ``` + +!!! Заметка + Заметьте, что в данном случае параметр `item`, который будет взят из тела запроса, необязателен. Так как было установлено значение `None` по умолчанию. + +## Несколько параметров тела запроса + +В предыдущем примере, *операции пути* ожидали тело запроса в формате JSON-тело с параметрами, соответствующими атрибутам `Item`, например: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +Но вы также можете объявить множество параметров тела запроса, например `item` и `user`: + +=== "Python 3.10+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="22" + {!> ../../../docs_src/body_multiple_params/tutorial002.py!} + ``` + +В этом случае **FastAPI** заметит, что в функции есть более одного параметра тела (два параметра, которые являются моделями Pydantic). + +Таким образом, имена параметров будут использоваться в качестве ключей (имён полей) в теле запроса, и будет ожидаться запрос следующего формата: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + } +} +``` + +!!! Внимание + Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предпологается, что он находится внутри тела с ключом `item`. + + +**FastAPI** сделает автоматические преобразование из запроса, так что параметр `item` получит своё конкретное содержимое, и то же самое происходит с пользователем `user`. + +Произойдёт проверка составных данных, и создание документации в схеме OpenAPI и автоматических документах. + +## Отдельные значения в теле запроса + +Точно так же, как `Query` и `Path` используются для определения дополнительных данных для query и path параметров, **FastAPI** предоставляет аналогичный инструмент - `Body`. + +Например, расширяя предыдущую модель, вы можете решить, что вам нужен еще один ключ `importance` в том же теле запроса, помимо параметров `item` и `user`. + +Если вы объявите его без указания, какой именно объект (Path, Query, Body и .т.п.) ожидаете, то, поскольку это является простым типом данных, **FastAPI** будет считать, что это query-параметр. + +Но вы можете указать **FastAPI** обрабатывать его, как ещё один ключ тела запроса, используя `Body`: + +=== "Python 3.10+" + + ```Python hl_lines="23" + {!> ../../../docs_src/body_multiple_params/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="23" + {!> ../../../docs_src/body_multiple_params/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24" + {!> ../../../docs_src/body_multiple_params/tutorial003_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! Заметка + Рекомендуется использовать `Annotated` версию, если это возможно. + + ```Python hl_lines="20" + {!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! Заметка + Рекомендуется использовать `Annotated` версию, если это возможно. + + ```Python hl_lines="22" + {!> ../../../docs_src/body_multiple_params/tutorial003.py!} + ``` + +В этом случае, **FastAPI** будет ожидать тело запроса в формате: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + }, + "importance": 5 +} +``` + +И всё будет работать так же - преобразование типов данных, валидация, документирование и т.д. + +## Множество body и query параметров + +Конечно, вы также можете объявлять query-параметры в любое время, дополнительно к любым body-параметрам. + +Поскольку по умолчанию, отдельные значения интерпретируются как query-параметры, вам не нужно явно добавлять `Query`, вы можете просто сделать так: + +```Python +q: Union[str, None] = None +``` + +Или в Python 3.10 и выше: + +```Python +q: str | None = None +``` + +Например: + +=== "Python 3.10+" + + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="28" + {!> ../../../docs_src/body_multiple_params/tutorial004_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! Заметка + Рекомендуется использовать `Annotated` версию, если это возможно. + + ```Python hl_lines="25" + {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! Заметка + Рекомендуется использовать `Annotated` версию, если это возможно. + + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004.py!} + ``` + +!!! Информация + `Body` также имеет все те же дополнительные параметры валидации и метаданных, как у `Query`,`Path` и других, которые вы увидите позже. + +## Добавление одного body-параметра + +Предположим, у вас есть только один body-параметр `item` из Pydantic модели `Item`. + +По умолчанию, **FastAPI** ожидает получить тело запроса напрямую. + +Но если вы хотите чтобы он ожидал JSON с ключом `item` с содержимым модели внутри, также как это происходит при объявлении дополнительных body-параметров, вы можете использовать специальный параметр `embed` у типа `Body`: + +```Python +item: Item = Body(embed=True) +``` + +так же, как в этом примере: + +=== "Python 3.10+" + + ```Python hl_lines="17" + {!> ../../../docs_src/body_multiple_params/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="17" + {!> ../../../docs_src/body_multiple_params/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_multiple_params/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! Заметка + Рекомендуется использовать `Annotated` версию, если это возможно. + + ```Python hl_lines="15" + {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! Заметка + Рекомендуется использовать `Annotated` версию, если это возможно. + + ```Python hl_lines="17" + {!> ../../../docs_src/body_multiple_params/tutorial005.py!} + ``` + +В этом случае **FastAPI** будет ожидать тело запроса в формате: + +```JSON hl_lines="2" +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + } +} +``` + +вместо этого: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +## Резюме + +Вы можете добавлять несколько body-параметров вашей *функции операции пути*, несмотря даже на то, что запрос может содержать только одно тело. + +Но **FastAPI** справится с этим, предоставит правильные данные в вашей функции, а также сделает валидацию и документацию правильной схемы *операции пути*. + +Вы также можете объявить отдельные значения для получения в рамках тела запроса. + +И вы можете настроить **FastAPI** таким образом, чтобы включить тело запроса в ключ, даже если объявлен только один параметр. diff --git a/docs/ru/docs/tutorial/body-nested-models.md b/docs/ru/docs/tutorial/body-nested-models.md new file mode 100644 index 0000000000..6435e316f4 --- /dev/null +++ b/docs/ru/docs/tutorial/body-nested-models.md @@ -0,0 +1,382 @@ +# Body - Вложенные модели + +С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic). + +## Определение полей содержащих списки + +Вы можете определять атрибут как подтип. Например, тип `list` в Python: + +=== "Python 3.10+" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial001.py!} + ``` + +Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен. + +## Определение полей содержащих список с определением типов его элементов + +Однако в Python есть способ объявления списков с указанием типов для вложенных элементов: + +### Импортируйте `List` из модуля typing + +В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡 + +Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python: + +```Python hl_lines="1" +{!> ../../../docs_src/body_nested_models/tutorial002.py!} +``` + +### Объявление `list` с указанием типов для вложенных элементов + +Объявление типов для элементов (внутренних типов) вложенных в такие типы как `list`, `dict`, `tuple`: + +* Если у вас Python версии ниже чем 3.9, импортируйте их аналог из модуля `typing` +* Передайте внутренний(ие) тип(ы) как "параметры типа", используя квадратные скобки: `[` и `]` + +В Python версии 3.9 это будет выглядеть так: + +```Python +my_list: list[str] +``` + +В версиях Python до 3.9 это будет выглядеть так: + +```Python +from typing import List + +my_list: List[str] +``` + +Это всё стандартный синтаксис Python для объявления типов. + +Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами. + +Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк": + +=== "Python 3.10+" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial002_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial002.py!} + ``` + +## Типы множеств + +Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками. + +И в Python есть специальный тип данных для множеств уникальных элементов - `set`. + +Тогда мы может обьявить поле `tags` как множество строк: + +=== "Python 3.10+" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial003_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 14" + {!> ../../../docs_src/body_nested_models/tutorial003.py!} + ``` + +С помощью этого, даже если вы получите запрос с повторяющимися данными, они будут преобразованы в множество уникальных элементов. + +И когда вы выводите эти данные, даже если исходный набор содержал дубликаты, они будут выведены в виде множества уникальных элементов. + +И они также будут соответствующим образом аннотированы / задокументированы. + +## Вложенные Модели + +У каждого атрибута Pydantic-модели есть тип. + +Но этот тип может сам быть другой моделью Pydantic. + +Таким образом вы можете объявлять глубоко вложенные JSON "объекты" с определёнными именами атрибутов, типами и валидацией. + +Всё это может быть произвольно вложенным. + +### Определение подмодели + +Например, мы можем определить модель `Image`: + +=== "Python 3.10+" + + ```Python hl_lines="7-9" + {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9-11" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-11" + {!> ../../../docs_src/body_nested_models/tutorial004.py!} + ``` + +### Использование вложенной модели в качестве типа + +Также мы можем использовать эту модель как тип атрибута: + +=== "Python 3.10+" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial004.py!} + ``` + +Это означает, что **FastAPI** будет ожидать тело запроса, аналогичное этому: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите: + +* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей +* Преобразование данных +* Валидацию данных +* Автоматическую документацию + +## Особые типы и валидация + +Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`. + +Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией по необычным типам Pydantic. Вы увидите некоторые примеры в следующей главе. + +Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`: + +=== "Python 3.10+" + + ```Python hl_lines="2 8" + {!> ../../../docs_src/body_nested_models/tutorial005_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 10" + {!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 10" + {!> ../../../docs_src/body_nested_models/tutorial005.py!} + ``` + +Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI. + +## Атрибуты, содержащие списки подмоделей + +Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д: + +=== "Python 3.10+" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_nested_models/tutorial006_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial006.py!} + ``` + +Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате: + +```JSON hl_lines="11" +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +!!! info "Информация" + Заметьте, что теперь у ключа `images` есть список объектов изображений. + +## Глубоко вложенные модели + +Вы можете определять модели с произвольным уровнем вложенности: + +=== "Python 3.10+" + + ```Python hl_lines="7 12 18 21 25" + {!> ../../../docs_src/body_nested_models/tutorial007_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9 14 20 23 27" + {!> ../../../docs_src/body_nested_models/tutorial007_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 14 20 23 27" + {!> ../../../docs_src/body_nested_models/tutorial007.py!} + ``` + +!!! info "Информация" + Заметьте, что у объекта `Offer` есть список объектов `Item`, которые, в свою очередь, могут содержать необязательный список объектов `Image` + +## Тела с чистыми списками элементов + +Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic: + +```Python +images: List[Image] +``` + +в Python 3.9 и выше: + +```Python +images: list[Image] +``` + +например так: + +=== "Python 3.9+" + + ```Python hl_lines="13" + {!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="15" + {!> ../../../docs_src/body_nested_models/tutorial008.py!} + ``` + +## Универсальная поддержка редактора + +И вы получаете поддержку редактора везде. + +Даже для элементов внутри списков: + + + +Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic. + +Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON. + +## Тела запросов с произвольными словарями (`dict` ) + +Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных. + +Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic). + +Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете. + +--- + +Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`. + +Именно это мы сейчас и увидим здесь. + +В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`: + +=== "Python 3.9+" + + ```Python hl_lines="7" + {!> ../../../docs_src/body_nested_models/tutorial009_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/body_nested_models/tutorial009.py!} + ``` + +!!! tip "Совет" + Имейте в виду, что JSON поддерживает только ключи типа `str`. + + Но Pydantic обеспечивает автоматическое преобразование данных. + + Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные. + + А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`. + +## Резюме + +С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода. + +И дополнительно вы получаете: + +* Поддержку редактора (автодополнение доступно везде!) +* Преобразование данных (также известно как парсинг / сериализация) +* Валидацию данных +* Документацию схемы данных +* Автоматическую генерацию документации diff --git a/docs/ru/docs/tutorial/body.md b/docs/ru/docs/tutorial/body.md new file mode 100644 index 0000000000..c03d40c3fb --- /dev/null +++ b/docs/ru/docs/tutorial/body.md @@ -0,0 +1,165 @@ +# Тело запроса + +Когда вам необходимо отправить данные из клиента (допустим, браузера) в ваш API, вы отправляете их как **тело запроса**. + +Тело **запроса** --- это данные, отправляемые клиентом в ваш API. Тело **ответа** --- это данные, которые ваш API отправляет клиенту. + +Ваш API почти всегда отправляет тело **ответа**. Но клиентам не обязательно всегда отправлять тело **запроса**. + +Чтобы объявить тело **запроса**, необходимо использовать модели Pydantic, со всей их мощью и преимуществами. + +!!! info "Информация" + Чтобы отправить данные, необходимо использовать один из методов: `POST` (обычно), `PUT`, `DELETE` или `PATCH`. + + Отправка тела с запросом `GET` имеет неопределенное поведение в спецификациях, тем не менее, оно поддерживается FastAPI только для очень сложных/экстремальных случаев использования. + + Поскольку это не рекомендуется, интерактивная документация со Swagger UI не будет отображать информацию для тела при использовании метода GET, а промежуточные прокси-серверы могут не поддерживать такой вариант запроса. + +## Импортирование `BaseModel` из Pydantic + +Первое, что вам необходимо сделать, это импортировать `BaseModel` из пакета `pydantic`: + +```Python hl_lines="4" +{!../../../docs_src/body/tutorial001.py!} +``` + +## Создание вашей собственной модели + +После этого вы описываете вашу модель данных как класс, наследующий от `BaseModel`. + +Используйте аннотации типов Python для всех атрибутов: + +```Python hl_lines="7-11" +{!../../../docs_src/body/tutorial001.py!} +``` + +Также как и при описании параметров запроса, когда атрибут модели имеет значение по умолчанию, он является необязательным. Иначе он обязателен. Используйте `None`, чтобы сделать его необязательным без использования конкретных значений по умолчанию. + +Например, модель выше описывает вот такой JSON "объект" (или словарь Python): + +```JSON +{ + "name": "Foo", + "description": "An optional description", + "price": 45.2, + "tax": 3.5 +} +``` + +...поскольку `description` и `tax` являются необязательными (с `None` в качестве значения по умолчанию), вот такой JSON "объект" также подходит: + +```JSON +{ + "name": "Foo", + "price": 45.2 +} +``` + +## Объявление как параметра функции + +Чтобы добавить параметр к вашему *обработчику*, объявите его также, как вы объявляли параметры пути или параметры запроса: + +```Python hl_lines="18" +{!../../../docs_src/body/tutorial001.py!} +``` + +...и укажите созданную модель в качестве типа параметра, `Item`. + +## Результаты + +Всего лишь с помощью аннотации типов Python, **FastAPI**: + +* Читает тело запроса как JSON. +* Приводит к соответствующим типам (если есть необходимость). +* Проверяет корректность данных. + * Если данные некорректны, будет возращена читаемая и понятная ошибка, показывающая что именно и в каком месте некорректно в данных. +* Складывает полученные данные в параметр `item`. + * Поскольку внутри функции вы объявили его с типом `Item`, то теперь у вас есть поддержка со стороны редактора (автодополнение и т.п.) для всех атрибутов и их типов. +* Генерирует декларативное описание модели в виде JSON Schema, так что вы можете его использовать где угодно, если это имеет значение для вашего проекта. +* Эти схемы являются частью сгенерированной схемы OpenAPI и используются для автоматического документирования UI. + +## Автоматическое документирование + +Схема JSON ваших моделей будет частью сгенерированной схемы OpenAPI и будет отображена в интерактивной документации API: + + + +Также она будет указана в документации по API внутри каждой *операции пути*, в которой используются: + + + +## Поддержка редактора + +В вашем редакторе внутри вашей функции у вас будут подсказки по типам и автодополнение (это не будет работать, если вы получаете словарь вместо модели Pydantic): + + + +Также вы будете получать ошибки в случае несоответствия типов: + + + +Это не случайно, весь фреймворк построен вокруг такого дизайна. + +И это все тщательно протестировано еще на этапе разработки дизайна, до реализации, чтобы это работало со всеми редакторами. + +Для поддержки этого даже были внесены некоторые изменения в сам Pydantic. + +На всех предыдущих скриншотах используется Visual Studio Code. + +Но у вас будет такая же поддержка и с PyCharm, и вообще с любым редактором Python: + + + +!!! tip "Подсказка" + Если вы используете PyCharm в качестве редактора, то вам стоит попробовать плагин Pydantic PyCharm Plugin. + + Он улучшает поддержку редактором моделей Pydantic в части: + + * автодополнения, + * проверки типов, + * рефакторинга, + * поиска, + * инспектирования. + +## Использование модели + +Внутри функции вам доступны все атрибуты объекта модели напрямую: + +```Python hl_lines="21" +{!../../../docs_src/body/tutorial002.py!} +``` + +## Тело запроса + параметры пути + +Вы можете одновременно объявлять параметры пути и тело запроса. + +**FastAPI** распознает, какие параметры функции соответствуют параметрам пути и должны быть **получены из пути**, а какие параметры функции, объявленные как модели Pydantic, должны быть **получены из тела запроса**. + +```Python hl_lines="17-18" +{!../../../docs_src/body/tutorial003.py!} +``` + +## Тело запроса + параметры пути + параметры запроса + +Вы также можете одновременно объявить параметры для **пути**, **запроса** и **тела запроса**. + +**FastAPI** распознает каждый из них и возьмет данные из правильного источника. + +```Python hl_lines="18" +{!../../../docs_src/body/tutorial004.py!} +``` + +Параметры функции распознаются следующим образом: + +* Если параметр также указан в **пути**, то он будет использоваться как параметр пути. +* Если аннотация типа параметра содержит **примитивный тип** (`int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**. +* Если аннотация типа параметра представляет собой **модель Pydantic**, он будет интерпретирован как параметр **тела запроса**. + +!!! note "Заметка" + FastAPI понимает, что значение параметра `q` не является обязательным, потому что имеет значение по умолчанию `= None`. + + Аннотация `Optional` в `Optional[str]` не используется FastAPI, но помогает вашему редактору лучше понимать ваш код и обнаруживать ошибки. + +## Без Pydantic + +Если вы не хотите использовать модели Pydantic, вы все еще можете использовать параметры **тела запроса**. Читайте в документации раздел [Тело - Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. diff --git a/docs/ru/docs/tutorial/cookie-params.md b/docs/ru/docs/tutorial/cookie-params.md new file mode 100644 index 0000000000..a6f2caa267 --- /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/docs/tutorial/cors.md b/docs/ru/docs/tutorial/cors.md new file mode 100644 index 0000000000..8c7fbc046d --- /dev/null +++ b/docs/ru/docs/tutorial/cors.md @@ -0,0 +1,84 @@ +# CORS (Cross-Origin Resource Sharing) + +Понятие CORS или "Cross-Origin Resource Sharing" относится к ситуациям, при которых запущенный в браузере фронтенд содержит JavaScript-код, который взаимодействует с бэкендом, находящимся на другом "источнике" ("origin"). + +## Источник + +Источник - это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`). + +Поэтому это три разных источника: + +* `http://localhost` +* `https://localhost` +* `http://localhost:8080` + +Даже если они все расположены в `localhost`, они используют разные протоколы и порты, а значит, являются разными источниками. + +## Шаги + +Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`). + +Затем браузер отправит бэкенду HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие заголовки для авторизации взаимодействия с другим источником (`http://localhost:8080`), то браузер разрешит JavaScript-коду на фронтенде отправить запрос на этот бэкенд. + +Чтобы это работало, у бэкенда должен быть список "разрешённых источников" ("allowed origins"). + +В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд работал корректно. + +## Подстановочный символ `"*"` + +В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники. + +Но тогда не будут разрешены некоторые виды взаимодействия, включая всё связанное с учётными данными: куки, заголовки Authorization с Bearer-токенами наподобие тех, которые мы использовали ранее и т.п. + +Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников. + +## Использование `CORSMiddleware` + +Вы можете настроить этот механизм в вашем **FastAPI** приложении, используя `CORSMiddleware`. + +* Импортируйте `CORSMiddleware`. +* Создайте список разрешённых источников (в виде строк). +* Добавьте его как "middleware" к вашему **FastAPI** приложению. + +Вы также можете указать, разрешает ли ваш бэкенд использование: + +* Учётных данных (включая заголовки Authorization, куки и т.п.). +* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`. +* Отдельных 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`. + +`CORSMiddleware` отвечает на два типа HTTP-запросов... + +### CORS-запросы с предварительной проверкой + +Это любые `OPTIONS` запросы с заголовками `Origin` и `Access-Control-Request-Method`. + +В этом случае middleware перехватит входящий запрос и отправит соответствующие CORS-заголовки в ответе, а также ответ `200` или `400` в информационных целях. + +### Простые запросы + +Любые запросы с заголовком `Origin`. В этом случае middleware передаст запрос дальше как обычно, но добавит соответствующие CORS-заголовки к ответу. + +## Больше информации + +Для получения более подробной информации о CORS, обратитесь к Документации CORS от Mozilla. + +!!! note "Технические детали" + Вы также можете использовать `from starlette.middleware.cors import CORSMiddleware`. + + **FastAPI** предоставляет несколько middleware в `fastapi.middleware` только для вашего удобства как разработчика. Но большинство доступных middleware взяты напрямую из Starlette. diff --git a/docs/ru/docs/tutorial/debugging.md b/docs/ru/docs/tutorial/debugging.md new file mode 100644 index 0000000000..755d98cf20 --- /dev/null +++ b/docs/ru/docs/tutorial/debugging.md @@ -0,0 +1,112 @@ +# Отладка + +Вы можете подключить отладчик в своем редакторе, например, в Visual Studio Code или PyCharm. + +## Вызов `uvicorn` + +В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую: + +```Python hl_lines="1 15" +{!../../../docs_src/debugging/tutorial001.py!} +``` + +### Описание `__name__ == "__main__"` + +Главная цель использования `__name__ == "__main__"` в том, чтобы код выполнялся при запуске файла с помощью: + +
+ +```console +$ python myapp.py +``` + +
+ +но не вызывался, когда другой файл импортирует это, например:: + +```Python +from myapp import app +``` + +#### Больше деталей + +Давайте назовём ваш файл `myapp.py`. + +Если вы запустите его с помощью: + +
+ +```console +$ python myapp.py +``` + +
+ +то встроенная переменная `__name__`, автоматически создаваемая Python в вашем файле, будет иметь значение строкового типа `"__main__"`. + +Тогда выполнится условие и эта часть кода: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +будет запущена. + +--- + +Но этого не произойдет, если вы импортируете этот модуль (файл). + +Таким образом, если у вас есть файл `importer.py` с таким импортом: + +```Python +from myapp import app + +# Some more code +``` + +то автоматическая создаваемая внутри файла `myapp.py` переменная `__name__` будет иметь значение отличающееся от `"__main__"`. + +Следовательно, строка: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +не будет выполнена. + +!!! Информация + Для получения дополнительной информации, ознакомьтесь с официальной документацией Python. + +## Запуск вашего кода с помощью отладчика + +Так как вы запускаете сервер Uvicorn непосредственно из вашего кода, вы можете вызвать Python программу (ваше FastAPI приложение) напрямую из отладчика. + +--- + +Например, в Visual Studio Code вы можете выполнить следующие шаги: + +* Перейдите на панель "Debug". +* Выберите "Add configuration...". +* Выберите "Python" +* Запустите отладчик "`Python: Current File (Integrated Terminal)`". + +Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д. + +Вот как это может выглядеть: + + + +--- + +Если используете Pycharm, вы можете выполнить следующие шаги: + +* Открыть "Run" меню. +* Выбрать опцию "Debug...". +* Затем в появившемся контекстном меню. +* Выбрать файл для отладки (в данном случае, `main.py`). + +Это запустит сервер с вашим **FastAPI** кодом, остановится на точках останова, и т.д. + +Вот как это может выглядеть: + + diff --git a/docs/ru/docs/tutorial/extra-data-types.md b/docs/ru/docs/tutorial/extra-data-types.md new file mode 100644 index 0000000000..efcbcb38a2 --- /dev/null +++ b/docs/ru/docs/tutorial/extra-data-types.md @@ -0,0 +1,82 @@ +# Дополнительные типы данных + +До сих пор вы использовали простые типы данных, такие как: + +* `int` +* `float` +* `str` +* `bool` + +Но вы также можете использовать и более сложные типы. + +При этом у вас останутся те же возможности , что и до сих пор: + +* Отличная поддержка редактора. +* Преобразование данных из входящих запросов. +* Преобразование данных для ответа. +* Валидация данных. +* Автоматическая аннотация и документация. + +## Другие типы данных + +Ниже перечислены некоторые из дополнительных типов данных, которые вы можете использовать: + +* `UUID`: + * Стандартный "Универсальный уникальный идентификатор", используемый в качестве идентификатора во многих базах данных и системах. + * В запросах и ответах будет представлен как `str`. +* `datetime.datetime`: + * Встроенный в Python `datetime.datetime`. + * В запросах и ответах будет представлен как `str` в формате ISO 8601, например: `2008-09-15T15:53:00+05:00`. +* `datetime.date`: + * Встроенный в Python `datetime.date`. + * В запросах и ответах будет представлен как `str` в формате ISO 8601, например: `2008-09-15`. +* `datetime.time`: + * Встроенный в Python `datetime.time`. + * В запросах и ответах будет представлен как `str` в формате ISO 8601, например: `14:23:55.003`. +* `datetime.timedelta`: + * Встроенный в Python `datetime.timedelta`. + * В запросах и ответах будет представлен в виде общего количества секунд типа `float`. + * Pydantic также позволяет представить его как "Кодировку разницы во времени ISO 8601", см. документацию для получения дополнительной информации. +* `frozenset`: + * В запросах и ответах обрабатывается так же, как и `set`: + * В запросах будет прочитан список, исключены дубликаты и преобразован в `set`. + * В ответах `set` будет преобразован в `list`. + * В сгенерированной схеме будет указано, что значения `set` уникальны (с помощью JSON-схемы `uniqueItems`). +* `bytes`: + * Встроенный в Python `bytes`. + * В запросах и ответах будет рассматриваться как `str`. + * В сгенерированной схеме будет указано, что это `str` в формате `binary`. +* `Decimal`: + * Встроенный в Python `Decimal`. + * В запросах и ответах обрабатывается так же, как и `float`. +* Вы можете проверить все допустимые типы данных pydantic здесь: Типы данных Pydantic. + +## Пример + +Вот пример *операции пути* с параметрами, который демонстрирует некоторые из вышеперечисленных типов. + +=== "Python 3.6 и выше" + + ```Python hl_lines="1 3 12-16" + {!> ../../../docs_src/extra_data_types/tutorial001.py!} + ``` + +=== "Python 3.10 и выше" + + ```Python hl_lines="1 2 11-15" + {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} + ``` + +Обратите внимание, что параметры внутри функции имеют свой естественный тип данных, и вы, например, можете выполнять обычные манипуляции с датами, такие как: + +=== "Python 3.6 и выше" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/extra_data_types/tutorial001.py!} + ``` + +=== "Python 3.10 и выше" + + ```Python hl_lines="17-18" + {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} + ``` diff --git a/docs/ru/docs/tutorial/extra-models.md b/docs/ru/docs/tutorial/extra-models.md new file mode 100644 index 0000000000..a346f7432c --- /dev/null +++ b/docs/ru/docs/tutorial/extra-models.md @@ -0,0 +1,252 @@ +# Дополнительные модели + +В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей. + +Это особенно применимо в случае моделей пользователя, потому что: + +* **Модель для ввода** должна иметь возможность содержать пароль. +* **Модель для вывода** не должна содержать пароль. +* **Модель для базы данных**, возможно, должна содержать хэшированный пароль. + +!!! danger "Внимание" + Никогда не храните пароли пользователей в чистом виде. Всегда храните "безопасный хэш", который вы затем сможете проверить. + + Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. + +## Множественные модели + +Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются: + +=== "Python 3.10+" + + ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" + {!> ../../../docs_src/extra_models/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" + {!> ../../../docs_src/extra_models/tutorial001.py!} + ``` + +### Про `**user_in.dict()` + +#### `.dict()` из Pydantic + +`user_in` - это Pydantic-модель класса `UserIn`. + +У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели. + +Поэтому, если мы создадим Pydantic-объект `user_in` таким способом: + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +и затем вызовем: + +```Python +user_dict = user_in.dict() +``` + +то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели). + +И если мы вызовем: + +```Python +print(user_dict) +``` + +мы можем получить `dict` с такими данными: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### Распаковка `dict` + +Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение. + +Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода: + +```Python +UserInDB(**user_dict) +``` + +Будет работать так же, как примерно такой код: + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### Pydantic-модель из содержимого другой модели + +Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код: + +```Python +user_dict = user_in.dict() +UserInDB(**user_dict) +``` + +будет равнозначен такому: + +```Python +UserInDB(**user_in.dict()) +``` + +...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`. + +Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели. + +#### Распаковка `dict` и дополнительные именованные аргументы + +И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь: + +```Python +UserInDB(**user_in.dict(), hashed_password=hashed_password) +``` + +... то мы получим что-то подобное: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +!!! warning "Предупреждение" + Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность. + +## Сократите дублирование + +Сокращение дублирования кода - это одна из главных идей **FastAPI**. + +Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д. + +А все описанные выше модели используют много общих данных и дублируют названия атрибутов и типов. + +Мы можем это улучшить. + +Мы можем определить модель `UserBase`, которая будет базовой для остальных моделей. И затем мы можем создать подклассы этой модели, которые будут наследовать её атрибуты (объявления типов, валидацию, и т.п.). + +Все операции конвертации, валидации, документации, и т.п. будут по-прежнему работать нормально. + +В этом случае мы можем определить только различия между моделями (с `password` в чистом виде, с `hashed_password` и без пароля): + +=== "Python 3.10+" + + ```Python hl_lines="7 13-14 17-18 21-22" + {!> ../../../docs_src/extra_models/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 15-16 19-20 23-24" + {!> ../../../docs_src/extra_models/tutorial002.py!} + ``` + +## `Union` или `anyOf` + +Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них. + +Он будет определён в OpenAPI как `anyOf`. + +Для этого используйте стандартные аннотации типов в Python `typing.Union`: + +!!! note "Примечание" + При объявлении `Union`, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`. + +=== "Python 3.10+" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003.py!} + ``` + +### `Union` в Python 3.10 + +В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`. + +Поскольку мы передаём его как **значение аргумента** вместо того, чтобы поместить его в **аннотацию типа**, нам придётся использовать `Union` даже в Python 3.10. + +Если оно было бы указано в аннотации типа, то мы могли бы использовать вертикальную черту как в примере: + +```Python +some_variable: PlaneItem | CarItem +``` + +Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа. + +## Список моделей + +Таким же образом вы можете определять ответы как списки объектов. + +Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше): + +=== "Python 3.9+" + + ```Python hl_lines="18" + {!> ../../../docs_src/extra_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 20" + {!> ../../../docs_src/extra_models/tutorial004.py!} + ``` + +## Ответ с произвольным `dict` + +Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей. + +Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели). + +В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше): + +=== "Python 3.9+" + + ```Python hl_lines="6" + {!> ../../../docs_src/extra_models/tutorial005_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 8" + {!> ../../../docs_src/extra_models/tutorial005.py!} + ``` + +## Резюме + +Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них. + +Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля. diff --git a/docs/ru/docs/tutorial/first-steps.md b/docs/ru/docs/tutorial/first-steps.md new file mode 100644 index 0000000000..b46f235bc7 --- /dev/null +++ b/docs/ru/docs/tutorial/first-steps.md @@ -0,0 +1,333 @@ +# Первые шаги + +Самый простой FastAPI файл может выглядеть так: + +```Python +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Скопируйте в файл `main.py`. + +Запустите сервер в режиме реального времени: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +!!! note "Технические детали" + Команда `uvicorn main:app` обращается к: + + * `main`: файл `main.py` (модуль Python). + * `app`: объект, созданный внутри файла `main.py` в строке `app = FastAPI()`. + * `--reload`: перезапускает сервер после изменения кода. Используйте только для разработки. + +В окне вывода появится следующая строка: + +```hl_lines="4" +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +Эта строка показывает URL-адрес, по которому приложение доступно на локальной машине. + +### Проверьте + +Откройте браузер по адресу: http://127.0.0.1:8000. + +Вы увидите JSON-ответ следующего вида: + +```JSON +{"message": "Hello World"} +``` + +### Интерактивная документация API + +Перейдите по адресу: http://127.0.0.1:8000/docs. + +Вы увидите автоматически сгенерированную, интерактивную документацию по API (предоставленную Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### Альтернативная документация API + +Теперь перейдите по адресу http://127.0.0.1:8000/redoc. + +Вы увидите альтернативную автоматически сгенерированную документацию (предоставленную ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +### OpenAPI + +**FastAPI** генерирует "схему" всего API, используя стандарт **OpenAPI**. + +#### "Схема" + +"Схема" - это определение или описание чего-либо. Не код, реализующий это, а только абстрактное описание. + +#### API "схема" + +OpenAPI - это спецификация, которая определяет, как описывать схему API. + +Определение схемы содержит пути (paths) API, их параметры и т.п. + +#### "Схема" данных + +Термин "схема" также может относиться к формату или структуре некоторых данных, например, JSON. + +Тогда, подразумеваются атрибуты JSON, их типы данных и т.п. + +#### OpenAPI и JSON Schema + +OpenAPI описывает схему API. Эта схема содержит определения (или "схемы") данных, отправляемых и получаемых API. Для описания структуры данных в JSON используется стандарт **JSON Schema**. + +#### Рассмотрим `openapi.json` + +Если Вас интересует, как выглядит исходная схема OpenAPI, то FastAPI автоматически генерирует JSON-схему со всеми описаниями API. + +Можете посмотреть здесь: http://127.0.0.1:8000/openapi.json. + +Вы увидите примерно такой JSON: + +```JSON +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +#### Для чего нужен OpenAPI + +Схема OpenAPI является основой для обеих систем интерактивной документации. + +Существуют десятки альтернативных инструментов, основанных на OpenAPI. Вы можете легко добавить любой из них к **FastAPI** приложению. + +Вы также можете использовать OpenAPI для автоматической генерации кода для клиентов, которые взаимодействуют с API. Например, для фронтенд-, мобильных или IoT-приложений. + +## Рассмотрим поэтапно + +### Шаг 1: импортируйте `FastAPI` + +```Python hl_lines="1" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`FastAPI` это класс в Python, который предоставляет всю функциональность для API. + +!!! note "Технические детали" + `FastAPI` это класс, который наследуется непосредственно от `Starlette`. + + Вы можете использовать всю функциональность Starlette в `FastAPI`. + +### Шаг 2: создайте экземпляр `FastAPI` + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Переменная `app` является экземпляром класса `FastAPI`. + +Это единая точка входа для создания и взаимодействия с API. + +Именно к этой переменной `app` обращается `uvicorn` в команде: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Если создать такое приложение: + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial002.py!} +``` + +И поместить его в `main.py`, тогда вызов `uvicorn` будет таким: + +
+ +```console +$ uvicorn main:my_awesome_api --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Шаг 3: определите *операцию пути (path operation)* + +#### Путь (path) + +"Путь" это часть URL, после первого символа `/`, следующего за именем домена. + +Для URL: + +``` +https://example.com/items/foo +``` + +...путь выглядит так: + +``` +/items/foo +``` + +!!! info "Дополнительная иформация" + Термин "path" также часто называется "endpoint" или "route". + +При создании API, "путь" является основным способом разделения "задач" и "ресурсов". + +#### Операция (operation) + +"Операция" это один из "методов" HTTP. + +Таких, как: + +* `POST` +* `GET` +* `PUT` +* `DELETE` + +...и более экзотических: + +* `OPTIONS` +* `HEAD` +* `PATCH` +* `TRACE` + +По протоколу HTTP можно обращаться к каждому пути, используя один (или несколько) из этих "методов". + +--- + +При создании API принято использовать конкретные HTTP-методы для выполнения определенных действий. + +Обычно используют: + +* `POST`: создать данные. +* `GET`: прочитать. +* `PUT`: изменить (обновить). +* `DELETE`: удалить. + +В OpenAPI каждый HTTP метод называется "**операция**". + +Мы также будем придерживаться этого термина. + +#### Определите *декоратор операции пути (path operation decorator)* + +```Python hl_lines="6" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Декоратор `@app.get("/")` указывает **FastAPI**, что функция, прямо под ним, отвечает за обработку запросов, поступающих по адресу: + +* путь `/` +* использующих get операцию + +!!! info "`@decorator` Дополнительная информация" + Синтаксис `@something` в Python называется "декоратор". + + Вы помещаете его над функцией. Как красивую декоративную шляпу (думаю, что оттуда и происходит этот термин). + + "Декоратор" принимает функцию ниже и выполняет с ней какое-то действие. + + В нашем случае, этот декоратор сообщает **FastAPI**, что функция ниже соответствует **пути** `/` и **операции** `get`. + + Это и есть "**декоратор операции пути**". + +Можно также использовать операции: + +* `@app.post()` +* `@app.put()` +* `@app.delete()` + +И более экзотические: + +* `@app.options()` +* `@app.head()` +* `@app.patch()` +* `@app.trace()` + +!!! tip "Подсказка" + Вы можете использовать каждую операцию (HTTP-метод) по своему усмотрению. + + **FastAPI** не навязывает определенного значения для каждого метода. + + Информация здесь представлена как рекомендация, а не требование. + + Например, при использовании GraphQL обычно все действия выполняются только с помощью POST операций. + +### Шаг 4: определите **функцию операции пути** + +Вот "**функция операции пути**": + +* **путь**: `/`. +* **операция**: `get`. +* **функция**: функция ниже "декоратора" (ниже `@app.get("/")`). + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Это обычная Python функция. + +**FastAPI** будет вызывать её каждый раз при получении `GET` запроса к URL "`/`". + +В данном случае это асинхронная функция. + +--- + +Вы также можете определить ее как обычную функцию вместо `async def`: + +```Python hl_lines="7" +{!../../../docs_src/first_steps/tutorial003.py!} +``` + +!!! note "Технические детали" + Если не знаете в чём разница, посмотрите [Конкурентность: *"Нет времени?"*](../async.md#in-a-hurry){.internal-link target=_blank}. + +### Шаг 5: верните результат + +```Python hl_lines="8" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д. + +Также можно вернуть модели Pydantic (рассмотрим это позже). + +Многие объекты и модели будут автоматически преобразованы в JSON (включая ORM). Пробуйте использовать другие объекты, которые предпочтительней для Вас, вероятно, они уже поддерживаются. + +## Резюме + +* Импортируем `FastAPI`. +* Создаём экземпляр `app`. +* Пишем **декоратор операции пути** (такой как `@app.get("/")`). +* Пишем **функцию операции пути** (`def root(): ...`). +* Запускаем сервер в режиме разработки (`uvicorn main:app --reload`). diff --git a/docs/ru/docs/tutorial/index.md b/docs/ru/docs/tutorial/index.md new file mode 100644 index 0000000000..ea3a1c37ae --- /dev/null +++ b/docs/ru/docs/tutorial/index.md @@ -0,0 +1,80 @@ +# Учебник - Руководство пользователя + +В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций. + +Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API. + +Он также создан для использования в качестве будущего справочника. + +Так что вы можете вернуться и посмотреть именно то, что вам нужно. + +## Запустите код + +Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python). + +Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +**НАСТОЯТЕЛЬНО рекомендуется**, чтобы вы написали или скопировали код, отредактировали его и запустили локально. + +Использование кода в вашем редакторе — это то, что действительно показывает вам преимущества FastAPI, видя, как мало кода вам нужно написать, все проверки типов, автодополнение и т.д. + +--- + +## Установка FastAPI + +Первый шаг — установить FastAPI. + +Для руководства вы, возможно, захотите установить его со всеми дополнительными зависимостями и функциями: + +
+ +```console +$ pip install "fastapi[all]" + +---> 100% +``` + +
+ +...это также включает `uvicorn`, который вы можете использовать в качестве сервера, который запускает ваш код. + +!!! note "Технические детали" + Вы также можете установить его по частям. + + Это то, что вы, вероятно, сделаете, когда захотите развернуть свое приложение в рабочей среде: + + ``` + pip install fastapi + ``` + + Также установите `uvicorn` для работы в качестве сервера: + + ``` + pip install "uvicorn[standard]" + ``` + + И то же самое для каждой из необязательных зависимостей, которые вы хотите использовать. + +## Продвинутое руководство пользователя + +Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**. + +**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям. + +Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас). + +Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**. diff --git a/docs/ru/docs/tutorial/metadata.md b/docs/ru/docs/tutorial/metadata.md new file mode 100644 index 0000000000..331c96734b --- /dev/null +++ b/docs/ru/docs/tutorial/metadata.md @@ -0,0 +1,111 @@ +# URL-адреса метаданных и документации + +Вы можете настроить несколько конфигураций метаданных в вашем **FastAPI** приложении. + +## Метаданные для API + +Вы можете задать следующие поля, которые используются в спецификации OpenAPI и в UI автоматической документации API: + +| Параметр | Тип | Описание | +|------------|--|-------------| +| `title` | `str` | Заголовок API. | +| `description` | `str` | Краткое описание API. Может быть использован Markdown. | +| `version` | `string` | Версия API. Версия вашего собственного приложения, а не OpenAPI. К примеру `2.5.0`. | +| `terms_of_service` | `str` | Ссылка к условиям пользования API. Если указано, то это должен быть URL-адрес. | +| `contact` | `dict` | Контактная информация для открытого API. Может содержать несколько полей.
поля contact
ПараметрТипОписание
namestrИдентификационное имя контактного лица/организации.
urlstrURL указывающий на контактную информацию. ДОЛЖЕН быть в формате URL.
emailstrEmail адрес контактного лица/организации. ДОЛЖЕН быть в формате email адреса.
| +| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей.
поля license_info
ПараметрТипОписание
namestrОБЯЗАТЕЛЬНО (если установлен параметр license_info). Название лицензии, используемой для API
urlstrURL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.
| + +Вы можете задать их следующим образом: + +```Python hl_lines="3-16 19-31" +{!../../../docs_src/metadata/tutorial001.py!} +``` + +!!! tip "Подсказка" + Вы можете использовать Markdown в поле `description`, и оно будет отображено в выводе. + +С этой конфигурацией автоматическая документация API будут выглядеть так: + + + +## Метаданные для тегов + +Вы также можете добавить дополнительные метаданные для различных тегов, используемых для группировки ваших операций пути с помощью параметра `openapi_tags`. + +Он принимает список, содержащий один словарь для каждого тега. + +Каждый словарь может содержать в себе: + +* `name` (**обязательно**): `str`-значение с тем же именем тега, которое вы используете в параметре `tags` в ваших *операциях пути* и `APIRouter`ах. +* `description`: `str`-значение с кратким описанием для тега. Может содержать Markdown и будет отображаться в UI документации. +* `externalDocs`: `dict`-значение описывающее внешнюю документацию. Включает в себя: + * `description`: `str`-значение с кратким описанием для внешней документации. + * `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации. + +### Создание метаданных для тегов + +Давайте попробуем сделать это на примере с тегами для `users` и `items`. + +Создайте метаданные для ваших тегов и передайте их в параметре `openapi_tags`: + +```Python hl_lines="3-16 18" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +Помните, что вы можете использовать Markdown внутри описания, к примеру "login" будет отображен жирным шрифтом (**login**) и "fancy" будет отображаться курсивом (_fancy_). + +!!! tip "Подсказка" + Вам необязательно добавлять метаданные для всех используемых тегов + +### Используйте собственные теги +Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги: + +```Python hl_lines="21 26" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +!!! info "Дополнительная информация" + Узнайте больше о тегах в [Конфигурации операции пути](../path-operation-configuration/#tags){.internal-link target=_blank}. + +### Проверьте документацию + +Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию: + + + +### Порядок расположения тегов + +Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в документах UI + +К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке. + +## URL-адреса OpenAPI + +По умолчанию схема OpenAPI отображена по адресу `/openapi.json`. + +Но вы можете изменить это с помощью параметра `openapi_url`. + +К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial002.py!} +``` + +Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые его использует. + +## URL-адреса документации + +Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, среди которых + +* **Swagger UI**: отображаемый по адресу `/docs`. + * Вы можете задать его URL с помощью параметра `docs_url`. + * Вы можете отключить это с помощью настройки `docs_url=None`. +* **ReDoc**: отображаемый по адресу `/redoc`. + * Вы можете задать его URL с помощью параметра `redoc_url`. + * Вы можете отключить это с помощью настройки `redoc_url=None`. + +К примеру, чтобы задать отображение Swagger UI по адресу `/documentation` и отключить ReDoc: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial003.py!} +``` diff --git a/docs/ru/docs/tutorial/path-operation-configuration.md b/docs/ru/docs/tutorial/path-operation-configuration.md new file mode 100644 index 0000000000..013903add1 --- /dev/null +++ b/docs/ru/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,179 @@ +# Конфигурация операций пути + +Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки. + +!!! warning "Внимание" + Помните, что эти параметры передаются непосредственно *декоратору операций пути*, а не вашей *функции-обработчику операций пути*. + +## Коды состояния + +Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*. + +Вы можете передать только `int`-значение кода, например `404`. + +Но если вы не помните, для чего нужен каждый числовой код, вы можете использовать сокращенные константы в параметре `status`: + +=== "Python 3.10+" + + ```Python hl_lines="1 15" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001.py!} + ``` + +Этот код состояния будет использован в ответе и будет добавлен в схему OpenAPI. + +!!! note "Технические детали" + Вы также можете использовать `from starlette import status`. + + **FastAPI** предоставляет тот же `starlette.status` под псевдонимом `fastapi.status` для удобства разработчика. Но его источник - это непосредственно Starlette. + +## Теги + +Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка): + +=== "Python 3.10+" + + ```Python hl_lines="15 20 25" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002.py!} + ``` + +Они будут добавлены в схему OpenAPI и будут использованы в автоматической документации интерфейса: + + + +### Теги с перечислениями + +Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*. + +В этих случаях, имеет смысл хранить теги в классе `Enum`. + +**FastAPI** поддерживает это так же, как и в случае с обычными строками: + +```Python hl_lines="1 8-10 13 18" +{!../../../docs_src/path_operation_configuration/tutorial002b.py!} +``` + +## Краткое и развёрнутое содержание + +Вы можете добавить параметры `summary` и `description`: + +=== "Python 3.10+" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003.py!} + ``` + +## Описание из строк документации + +Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции строки документации и **FastAPI** прочитает её отсюда. + +Вы можете использовать Markdown в строке документации, и он будет интерпретирован и отображён корректно (с учетом отступа в строке документации). + +=== "Python 3.10+" + + ```Python hl_lines="17-25" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004.py!} + ``` + +Он будет использован в интерактивной документации: + + + +## Описание ответа + +Вы можете указать описание ответа с помощью параметра `response_description`: + +=== "Python 3.10+" + + ```Python hl_lines="19" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005.py!} + ``` + +!!! info "Дополнительная информация" + Помните, что `response_description` относится конкретно к ответу, а `description` относится к *операции пути* в целом. + +!!! check "Технические детали" + OpenAPI указывает, что каждой *операции пути* необходимо описание ответа. + + Если вдруг вы не укажете его, то **FastAPI** автоматически сгенерирует это описание с текстом "Successful response". + + + +## Обозначение *операции пути* как устаревшей + +Если вам необходимо пометить *операцию пути* как устаревшую, при этом не удаляя её, передайте параметр `deprecated`: + +```Python hl_lines="16" +{!../../../docs_src/path_operation_configuration/tutorial006.py!} +``` + +Он будет четко помечен как устаревший в интерактивной документации: + + + +Проверьте, как будут выглядеть устаревшие и не устаревшие *операции пути*: + + + +## Резюме + +Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*. diff --git a/docs/ru/docs/tutorial/path-params-numeric-validations.md b/docs/ru/docs/tutorial/path-params-numeric-validations.md new file mode 100644 index 0000000000..0d034ef343 --- /dev/null +++ b/docs/ru/docs/tutorial/path-params-numeric-validations.md @@ -0,0 +1,292 @@ +# Path-параметры и валидация числовых данных + +Так же, как с помощью `Query` вы можете добавлять валидацию и метаданные для query-параметров, так и с помощью `Path` вы можете добавлять такую же валидацию и метаданные для path-параметров. + +## Импорт Path + +Сначала импортируйте `Path` из `fastapi`, а также импортируйте `Annotated`: + +=== "Python 3.10+" + + ```Python hl_lines="1 3" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="1 3" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3-4" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} + ``` + +=== "Python 3.10+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="1" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="3" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + +!!! info "Информация" + Поддержка `Annotated` была добавлена в FastAPI начиная с версии 0.95.0 (и с этой версии рекомендуется использовать этот подход). + + Если вы используете более старую версию, вы столкнётесь с ошибками при попытке использовать `Annotated`. + + Убедитесь, что вы [обновили версию FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} как минимум до 0.95.1 перед тем, как использовать `Annotated`. + +## Определите метаданные + +Вы можете указать все те же параметры, что и для `Query`. + +Например, чтобы указать значение метаданных `title` для path-параметра `item_id`, вы можете написать: + +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} + ``` + +=== "Python 3.10+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="8" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + +!!! note "Примечание" + Path-параметр всегда является обязательным, поскольку он составляет часть пути. + + Поэтому следует объявить его с помощью `...`, чтобы обозначить, что этот параметр обязательный. + + Тем не менее, даже если вы объявите его как `None` или установите для него значение по умолчанию, это ни на что не повлияет и параметр останется обязательным. + +## Задайте нужный вам порядок параметров + +!!! tip "Подсказка" + Это не имеет большого значения, если вы используете `Annotated`. + +Допустим, вы хотите объявить query-параметр `q` как обязательный параметр типа `str`. + +И если вам больше ничего не нужно указывать для этого параметра, то нет необходимости использовать `Query`. + +Но вам по-прежнему нужно использовать `Path` для path-параметра `item_id`. И если по какой-либо причине вы не хотите использовать `Annotated`, то могут возникнуть небольшие сложности. + +Если вы поместите параметр со значением по умолчанию перед другим параметром, у которого нет значения по умолчанию, то Python укажет на ошибку. + +Но вы можете изменить порядок параметров, чтобы параметр без значения по умолчанию (query-параметр `q`) шёл первым. + +Это не имеет значения для **FastAPI**. Он распознает параметры по их названиям, типам и значениям по умолчанию (`Query`, `Path`, и т.д.), ему не важен их порядок. + +Поэтому вы можете определить функцию так: + +=== "Python 3.6 без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/path_params_numeric_validations/tutorial002.py!} + ``` + +Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете `Query()` или `Path()` в качестве значения по умолчанию для параметра функции. + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/path_params_numeric_validations/tutorial002_an.py!} + ``` + +## Задайте нужный вам порядок параметров, полезные приёмы + +!!! tip "Подсказка" + Это не имеет большого значения, если вы используете `Annotated`. + +Здесь описан **небольшой приём**, который может оказаться удобным, хотя часто он вам не понадобится. + +Если вы хотите: + +* объявить query-параметр `q` без `Query` и без значения по умолчанию +* объявить path-параметр `item_id` с помощью `Path` +* указать их в другом порядке +* не использовать `Annotated` + +...то вы можете использовать специальную возможность синтаксиса Python. + +Передайте `*` в качестве первого параметра функции. + +Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как kwargs, даже если у них нет значений по умолчанию. + +```Python hl_lines="7" +{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} +``` + +### Лучше с `Annotated` + +Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, то у вас не возникнет подобной проблемы и вам не придётся использовать `*`. + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/path_params_numeric_validations/tutorial003_an.py!} + ``` + +## Валидация числовых данных: больше или равно + +С помощью `Query` и `Path` (и других классов, которые мы разберём позже) вы можете добавлять ограничения для числовых данных. + +В этом примере при указании `ge=1`, параметр `item_id` должен быть больше или равен `1` ("`g`reater than or `e`qual"). + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/path_params_numeric_validations/tutorial004_an.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="8" + {!> ../../../docs_src/path_params_numeric_validations/tutorial004.py!} + ``` + +## Валидация числовых данных: больше и меньше или равно + +То же самое применимо к: + +* `gt`: больше (`g`reater `t`han) +* `le`: меньше или равно (`l`ess than or `e`qual) + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/path_params_numeric_validations/tutorial005_an.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="9" + {!> ../../../docs_src/path_params_numeric_validations/tutorial005.py!} + ``` + +## Валидация числовых данных: числа с плавающей точкой, больше и меньше + +Валидация также применима к значениям типа `float`. + +В этом случае становится важной возможность добавить ограничение gt, вместо ge, поскольку в таком случае вы можете, например, создать ограничение, чтобы значение было больше `0`, даже если оно меньше `1`. + +Таким образом, `0.5` будет корректным значением. А `0.0` или `0` — нет. + +То же самое справедливо и для lt. + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="11" + {!> ../../../docs_src/path_params_numeric_validations/tutorial006.py!} + ``` + +## Резюме + +С помощью `Query`, `Path` (и других классов, которые мы пока не затронули) вы можете добавлять метаданные и строковую валидацию тем же способом, как и в главе [Query-параметры и валидация строк](query-params-str-validations.md){.internal-link target=_blank}. + +А также вы можете добавить валидацию числовых данных: + +* `gt`: больше (`g`reater `t`han) +* `ge`: больше или равно (`g`reater than or `e`qual) +* `lt`: меньше (`l`ess `t`han) +* `le`: меньше или равно (`l`ess than or `e`qual) + +!!! info "Информация" + `Query`, `Path` и другие классы, которые мы разберём позже, являются наследниками общего класса `Param`. + + Все они используют те же параметры для дополнительной валидации и метаданных, которые вы видели ранее. + +!!! note "Технические детали" + `Query`, `Path` и другие "классы", которые вы импортируете из `fastapi`, на самом деле являются функциями, которые при вызове возвращают экземпляры одноимённых классов. + + Объект `Query`, который вы импортируете, является функцией. И при вызове она возвращает экземпляр одноимённого класса `Query`. + + Использование функций (вместо использования классов напрямую) нужно для того, чтобы ваш редактор не подсвечивал ошибки, связанные с их типами. + + Таким образом вы можете использовать привычный вам редактор и инструменты разработки, не добавляя дополнительных конфигураций для игнорирования подобных ошибок. diff --git a/docs/ru/docs/tutorial/path-params.md b/docs/ru/docs/tutorial/path-params.md new file mode 100644 index 0000000000..55b498ef0a --- /dev/null +++ b/docs/ru/docs/tutorial/path-params.md @@ -0,0 +1,251 @@ +# Path-параметры + +Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python: + +```Python hl_lines="6-7" +{!../../../docs_src/path_params/tutorial001.py!} +``` + +Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`. + +Если запустите этот пример и перейдёте по адресу: http://127.0.0.1:8000/items/foo, то увидите ответ: + +```JSON +{"item_id":"foo"} +``` + +## Параметры пути с типами + +Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python. + +```Python hl_lines="7" +{!../../../docs_src/path_params/tutorial002.py!} +``` + +Здесь, `item_id` объявлен типом `int`. + +!!! check "Заметка" + Это обеспечит поддержку редактора внутри функции (проверка ошибок, автодополнение и т.п.). + +## Преобразование данных + +Если запустите этот пример и перейдёте по адресу: http://127.0.0.1:8000/items/3, то увидите ответ: + +```JSON +{"item_id":3} +``` + +!!! check "Заметка" + Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`. + + Используя определения типов, **FastAPI** выполняет автоматический "парсинг" запросов. + +## Проверка данных + +Если откроете браузер по адресу http://127.0.0.1:8000/items/foo, то увидите интересную HTTP-ошибку: + +```JSON +{ + "detail": [ + { + "loc": [ + "path", + "item_id" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +из-за того, что параметр пути `item_id` имеет значение `"foo"`, которое не является типом `int`. + +Та же ошибка возникнет, если вместо `int` передать `float` , например: http://127.0.0.1:8000/items/4.2 + +!!! check "Заметка" + **FastAPI** обеспечивает проверку типов, используя всё те же определения типов. + + Обратите внимание, что в тексте ошибки явно указано место не прошедшее проверку. + + Это очень полезно при разработке и отладке кода, который взаимодействует с API. + +## Документация + +И теперь, когда откроете браузер по адресу: http://127.0.0.1:8000/docs, то увидите вот такую автоматически сгенерированную документацию API: + + + +!!! check "Заметка" + Ещё раз, просто используя определения типов, **FastAPI** обеспечивает автоматическую интерактивную документацию (с интеграцией Swagger UI). + + Обратите внимание, что параметр пути объявлен целочисленным. + +## Преимущества стандартизации, альтернативная документация + +Поскольку сгенерированная схема соответствует стандарту OpenAPI, её можно использовать со множеством совместимых инструментов. + +Именно поэтому, FastAPI сам предоставляет альтернативную документацию API (используя ReDoc), которую можно получить по адресу: http://127.0.0.1:8000/redoc. + + + +По той же причине, есть множество совместимых инструментов, включая инструменты генерации кода для многих языков. + +## Pydantic + +Вся проверка данных выполняется под капотом с помощью Pydantic. Поэтому вы можете быть уверены в качестве обработки данных. + +Вы можете использовать в аннотациях как простые типы данных, вроде `str`, `float`, `bool`, так и более сложные типы. + +Некоторые из них рассматриваются в следующих главах данного руководства. + +## Порядок имеет значение + +При создании *операций пути* можно столкнуться с ситуацией, когда путь является фиксированным. + +Например, `/users/me`. Предположим, что это путь для получения данных о текущем пользователе. + +У вас также может быть путь `/users/{user_id}`, чтобы получить данные о конкретном пользователе по его ID. + +Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`: + + +```Python hl_lines="6 11" +{!../../../docs_src/path_params/tutorial003.py!} +``` + +Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`. + +Аналогично, вы не можете переопределить операцию с путем: + +```Python hl_lines="6 11" +{!../../../docs_src/path_params/tutorial003b.py!} +``` + +Первый будет выполняться всегда, так как путь совпадает первым. + +## Предопределенные значения + +Что если нам нужно заранее определить допустимые *параметры пути*, которые *операция пути* может принимать? В таком случае можно использовать стандартное перечисление `Enum` Python. + +### Создание класса `Enum` + +Импортируйте `Enum` и создайте подкласс, который наследуется от `str` и `Enum`. + +Мы наследуемся от `str`, чтобы документация API могла понять, что значения должны быть типа `string` и отображалась правильно. + +Затем создайте атрибуты класса с фиксированными допустимыми значениями: + +```Python hl_lines="1 6-9" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +!!! info "Дополнительная информация" + Перечисления (enum) доступны в Python начиная с версии 3.4. + +!!! tip "Подсказка" + Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия моделей машинного обучения. + +### Определение *параметра пути* + +Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее: + +```Python hl_lines="16" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +### Проверьте документацию + +Поскольку доступные значения *параметра пути* определены заранее, интерактивная документация может наглядно их отображать: + + + +### Работа с *перечислениями* в Python + +Значение *параметра пути* будет *элементом перечисления*. + +#### Сравнение *элементов перечисления* + +Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`: + +```Python hl_lines="17" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +#### Получение *значения перечисления* + +Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`: + +```Python hl_lines="20" +{!../../../docs_src/path_params/tutorial005.py!} +``` + +!!! tip "Подсказка" + Значение `"lenet"` также можно получить с помощью `ModelName.lenet.value`. + +#### Возврат *элементов перечисления* + +Из *операции пути* можно вернуть *элементы перечисления*, даже вложенные в тело JSON (например в `dict`). + +Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту: + +```Python hl_lines="18 21 23" +{!../../../docs_src/path_params/tutorial005.py!} +``` +Вы отправите клиенту такой JSON-ответ: + +```JSON +{ + "model_name": "alexnet", + "message": "Deep Learning FTW!" +} +``` + +## Path-параметры, содержащие пути + +Предположим, что есть *операция пути* с путем `/files/{file_path}`. + +Но вам нужно, чтобы `file_path` сам содержал *путь*, например, `home/johndoe/myfile.txt`. + +Тогда URL для этого файла будет такой: `/files/home/johndoe/myfile.txt`. + +### Поддержка OpenAPI + +OpenAPI не поддерживает способов объявления *параметра пути*, содержащего внутри *путь*, так как это может привести к сценариям, которые сложно определять и тестировать. + +Тем не менее это можно сделать в **FastAPI**, используя один из внутренних инструментов Starlette. + +Документация по-прежнему будет работать, хотя и не добавит никакой информации о том, что параметр должен содержать путь. + +### Конвертер пути + +Благодаря одной из опций Starlette, можете объявить *параметр пути*, содержащий *путь*, используя URL вроде: + +``` +/files/{file_path:path} +``` + +В этом случае `file_path` - это имя параметра, а часть `:path`, указывает, что параметр должен соответствовать любому *пути*. + +Можете использовать так: + +```Python hl_lines="6" +{!../../../docs_src/path_params/tutorial004.py!} +``` + +!!! tip "Подсказка" + Возможно, вам понадобится, чтобы параметр содержал `/home/johndoe/myfile.txt` с ведущим слэшем (`/`). + + В этом случае URL будет таким: `/files//home/johndoe/myfile.txt`, с двойным слэшем (`//`) между `files` и `home`. + +## Резюме +Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете: + +* Поддержку редактора (проверку ошибок, автозаполнение и т.п.) +* "Парсинг" данных +* Валидацию данных +* Автоматическую документацию API с указанием типов параметров. + +И объявлять типы достаточно один раз. + +Это, вероятно, является главным заметным преимуществом **FastAPI** по сравнению с альтернативными фреймворками (кроме сырой производительности). diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md new file mode 100644 index 0000000000..68042db639 --- /dev/null +++ b/docs/ru/docs/tutorial/query-params-str-validations.md @@ -0,0 +1,919 @@ +# Query-параметры и валидация строк + +**FastAPI** позволяет определять дополнительную информацию и валидацию для ваших параметров. + +Давайте рассмотрим следующий пример: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial001.py!} + ``` + +Query-параметр `q` имеет тип `Union[str, None]` (или `str | None` в Python 3.10). Это означает, что входной параметр будет типа `str`, но может быть и `None`. Ещё параметр имеет значение по умолчанию `None`, из-за чего FastAPI определит параметр как необязательный. + +!!! note "Технические детали" + FastAPI определит параметр `q` как необязательный, потому что его значение по умолчанию `= None`. + + `Union` в `Union[str, None]` позволит редактору кода оказать вам лучшую поддержку и найти ошибки. + +## Расширенная валидация + +Добавим дополнительное условие валидации параметра `q` - **длина строки не более 50 символов** (условие проверяется всякий раз, когда параметр `q` не является `None`). + +### Импорт `Query` и `Annotated` + +Чтобы достичь этого, первым делом нам нужно импортировать: + +* `Query` из пакета `fastapi`: +* `Annotated` из пакета `typing` (или из `typing_extensions` для Python ниже 3.9) + +=== "Python 3.10+" + + В Python 3.9 или выше, `Annotated` является частью стандартной библиотеки, таким образом вы можете импортировать его из `typing`. + + ```Python hl_lines="1 3" + {!> ../../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} + ``` + +=== "Python 3.6+" + + В версиях Python ниже Python 3.9 `Annotation` импортируется из `typing_extensions`. + + Эта библиотека будет установлена вместе с FastAPI. + + ```Python hl_lines="3-4" + {!> ../../../docs_src/query_params_str_validations/tutorial002_an.py!} + ``` + +## `Annotated` как тип для query-параметра `q` + +Помните, как ранее я говорил об Annotated? Он может быть использован для добавления метаданных для ваших параметров в разделе [Введение в аннотации типов Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}? + +Пришло время использовать их в FastAPI. 🚀 + +У нас была аннотация следующего типа: + +=== "Python 3.10+" + + ```Python + q: str | None = None + ``` + +=== "Python 3.6+" + + ```Python + q: Union[str, None] = None + ``` + +Вот что мы получим, если обернём это в `Annotated`: + +=== "Python 3.10+" + + ```Python + q: Annotated[str | None] = None + ``` + +=== "Python 3.6+" + + ```Python + q: Annotated[Union[str, None]] = None + ``` + +Обе эти версии означают одно и тоже. `q` - это параметр, который может быть `str` или `None`, и по умолчанию он будет принимать `None`. + +Давайте повеселимся. 🎉 + +## Добавим `Query` в `Annotated` для query-параметра `q` + +Теперь, когда у нас есть `Annotated`, где мы можем добавить больше метаданных, добавим `Query` со значением параметра `max_length` равным 50: + +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial002_an.py!} + ``` + +Обратите внимание, что значение по умолчанию всё ещё `None`, так что параметр остаётся необязательным. + +Однако теперь, имея `Query(max_length=50)` внутри `Annotated`, мы говорим FastAPI, что мы хотим извлечь это значение из параметров query-запроса (что произойдёт в любом случае 🤷), и что мы хотим иметь **дополнительные условия валидации** для этого значения (для чего мы и делаем это - чтобы получить дополнительную валидацию). 😎 + +Теперь FastAPI: + +* **Валидирует** (проверяет), что полученные данные состоят максимум из 50 символов +* Показывает **исчерпывающую ошибку** (будет описание местонахождения ошибки и её причины) для клиента в случаях, когда данные не валидны +* **Задокументирует** параметр в схему OpenAPI *операции пути* (что будет отображено в **UI автоматической документации**) + +## Альтернативный (устаревший) способ задать `Query` как значение по умолчанию + +В предыдущих версиях FastAPI (ниже 0.95.0) необходимо было использовать `Query` как значение по умолчанию для query-параметра. Так было вместо размещения его в `Annotated`, так что велика вероятность, что вам встретится такой код. Сейчас объясню. + +!!! tip "Подсказка" + При написании нового кода и везде где это возможно, используйте `Annotated`, как было описано ранее. У этого способа есть несколько преимуществ (о них дальше) и никаких недостатков. 🍰 + +Вот как вы могли бы использовать `Query()` в качестве значения по умолчанию параметра вашей функции, установив для параметра `max_length` значение 50: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial002.py!} + ``` + +В таком случае (без использования `Annotated`), мы заменили значение по умолчанию с `None` на `Query()` в функции. Теперь нам нужно установить значение по умолчанию для query-параметра `Query(default=None)`, что необходимо для тех же целей, как когда ранее просто указывалось значение по умолчанию (по крайней мере, для FastAPI). + +Таким образом: + +```Python +q: Union[str, None] = Query(default=None) +``` + +...делает параметр необязательным со значением по умолчанию `None`, также как это делает: + +```Python +q: Union[str, None] = None +``` + +И для Python 3.10 и выше: + +```Python +q: str | None = Query(default=None) +``` + +...делает параметр необязательным со значением по умолчанию `None`, также как это делает: + +```Python +q: str | None = None +``` + +Но он явно объявляет его как query-параметр. + +!!! info "Дополнительная информация" + Запомните, важной частью объявления параметра как необязательного является: + + ```Python + = None + ``` + + или: + + ```Python + = Query(default=None) + ``` + + так как `None` указан в качестве значения по умолчанию, параметр будет **необязательным**. + + `Union[str, None]` позволит редактору кода оказать вам лучшую поддержку. Но это не то, на что обращает внимание FastAPI для определения необязательности параметра. + +Теперь, мы можем указать больше параметров для `Query`. В данном случае, параметр `max_length` применяется к строкам: + +```Python +q: Union[str, None] = Query(default=None, max_length=50) +``` + +Входные данные будут проверены. Если данные недействительны, тогда будет указано на ошибку в запросе (будет описание местонахождения ошибки и её причины). Кроме того, параметр задокументируется в схеме OpenAPI данной *операции пути*. + +### Использовать `Query` как значение по умолчанию или добавить в `Annotated` + +Когда `Query` используется внутри `Annotated`, вы не можете использовать параметр `default` у `Query`. + +Вместо этого, используйте обычное указание значения по умолчанию для параметра функции. Иначе, это будет несовместимо. + +Следующий пример не рабочий: + +```Python +q: Annotated[str, Query(default="rick")] = "morty" +``` + +...потому что нельзя однозначно определить, что именно должно быть значением по умолчанию: `"rick"` или `"morty"`. + +Вам следует использовать (предпочтительно): + +```Python +q: Annotated[str, Query()] = "rick" +``` + +...или как в старом коде, который вам может попасться: + +```Python +q: str = Query(default="rick") +``` + +### Преимущества `Annotated` + +**Рекомендуется использовать `Annotated`** вместо значения по умолчанию в параметрах функции, потому что так **лучше** по нескольким причинам. 🤓 + +Значение **по умолчанию** у **параметров функции** - это **действительно значение по умолчанию**, что более интуитивно понятно для пользователей Python. 😌 + +Вы можете **вызвать** ту же функцию в **иных местах** без FastAPI, и она **сработает как ожидается**. Если это **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке. **Python** также укажет на ошибку, если вы вызовете функцию без передачи ей обязательного параметра. + +Если вы вместо `Annotated` используете **(устаревший) стиль значений по умолчанию**, тогда при вызове этой функции без FastAPI в **другом месте** вам необходимо **помнить** о передаче аргументов функции, чтобы она работала корректно. В противном случае, значения будут отличаться от тех, что вы ожидаете (например, `QueryInfo` или что-то подобное вместо `str`). И ни ваш редактор кода, ни Python не будут жаловаться на работу этой функции, только когда вычисления внутри дадут сбой. + +Так как `Annotated` может принимать более одной аннотации метаданных, то теперь вы можете использовать ту же функцию с другими инструментами, например Typer. 🚀 + +## Больше валидации + +Вы также можете добавить параметр `min_length`: + +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial003.py!} + ``` + +## Регулярные выражения + +Вы можете определить регулярное выражение, которому должен соответствовать параметр: + +=== "Python 3.10+" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial004_an_py310.py!} + ``` + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial004_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial004.py!} + ``` + +Данное регулярное выражение проверяет, что полученное значение параметра: + +* `^`: начало строки. +* `fixedquery`: в точности содержит строку `fixedquery`. +* `$`: конец строки, не имеет символов после `fixedquery`. + +Не переживайте, если **"регулярное выражение"** вызывает у вас трудности. Это достаточно сложная тема для многих людей. Вы можете сделать множество вещей без использования регулярных выражений. + +Но когда они вам понадобятся, и вы закончите их освоение, то не будет проблемой использовать их в **FastAPI**. + +## Значения по умолчанию + +Вы точно также можете указать любое значение `по умолчанию`, как ранее указывали `None`. + +Например, вы хотите для параметра запроса `q` указать, что он должен состоять минимум из 3 символов (`min_length=3`) и иметь значение по умолчанию `"fixedquery"`: + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial005.py!} + ``` + +!!! note "Технические детали" + Наличие значения по умолчанию делает параметр необязательным. + +## Обязательный параметр + +Когда вам не требуется дополнительная валидация или дополнительные метаданные для параметра запроса, вы можете сделать параметр `q` обязательным просто не указывая значения по умолчанию. Например: + +```Python +q: str +``` + +вместо: + +```Python +q: Union[str, None] = None +``` + +Но у нас query-параметр определён как `Query`. Например: + +=== "Annotated" + + ```Python + q: Annotated[Union[str, None], Query(min_length=3)] = None + ``` + +=== "без Annotated" + + ```Python + q: Union[str, None] = Query(default=None, min_length=3) + ``` + +В таком случае, чтобы сделать query-параметр `Query` обязательным, вы можете просто не указывать значение по умолчанию: + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial006.py!} + ``` + + !!! tip "Подсказка" + Обратите внимание, что даже когда `Query()` используется как значение по умолчанию для параметра функции, мы не передаём `default=None` в `Query()`. + + Лучше будет использовать версию с `Annotated`. 😉 + +### Обязательный параметр с Ellipsis (`...`) + +Альтернативный способ указать обязательность параметра запроса - это указать параметр `default` через многоточие `...`: + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial006b.py!} + ``` + +!!! info "Дополнительная информация" + Если вы ранее не сталкивались с `...`: это специальное значение, часть языка Python и называется "Ellipsis". + + Используется в Pydantic и FastAPI для определения, что значение требуется обязательно. + +Таким образом, **FastAPI** определяет, что параметр является обязательным. + +### Обязательный параметр с `None` + +Вы можете определить, что параметр может принимать `None`, но всё ещё является обязательным. Это может потребоваться для того, чтобы пользователи явно указали параметр, даже если его значение будет `None`. + +Чтобы этого добиться, вам нужно определить `None` как валидный тип для параметра запроса, но также указать `default=...`: + +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial006c_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial006c.py!} + ``` + +!!! tip "Подсказка" + Pydantic, мощь которого используется в FastAPI для валидации и сериализации, имеет специальное поведение для `Optional` или `Union[Something, None]` без значения по умолчанию. Вы можете узнать об этом больше в документации Pydantic, раздел Обязательные Опциональные поля. + +### Использование Pydantic's `Required` вместо Ellipsis (`...`) + +Если вас смущает `...`, вы можете использовать `Required` из Pydantic: + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="2 8" + {!> ../../../docs_src/query_params_str_validations/tutorial006d.py!} + ``` + +!!! tip "Подсказка" + Запомните, когда вам необходимо объявить query-параметр обязательным, вы можете просто не указывать параметр `default`. Таким образом, вам редко придётся использовать `...` или `Required`. + +## Множество значений для query-параметра + +Для query-параметра `Query` можно указать, что он принимает список значений (множество значений). + +Например, query-параметр `q` может быть указан в URL несколько раз. И если вы ожидаете такой формат запроса, то можете указать это следующим образом: + +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial011_an.py!} + ``` + +=== "Python 3.10+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!} + ``` + +=== "Python 3.9+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011.py!} + ``` + +Затем, получив такой URL: + +``` +http://localhost:8000/items/?q=foo&q=bar +``` + +вы бы получили несколько значений (`foo` и `bar`), которые относятся к параметру `q`, в виде Python `list` внутри вашей *функции обработки пути*, в *параметре функции* `q`. + +Таким образом, ответ на этот URL будет: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +!!! tip "Подсказка" + Чтобы объявить query-параметр типом `list`, как в примере выше, вам нужно явно использовать `Query`, иначе он будет интерпретирован как тело запроса. + +Интерактивная документация API будет обновлена соответствующим образом, где будет разрешено множество значений: + + + +### Query-параметр со множеством значений по умолчанию + +Вы также можете указать тип `list` со списком значений по умолчанию на случай, если вам их не предоставят: + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial012_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial012_an.py!} + ``` + +=== "Python 3.9+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial012.py!} + ``` + +Если вы перейдёте по ссылке: + +``` +http://localhost:8000/items/ +``` + +значение по умолчанию для `q` будет: `["foo", "bar"]` и ответом для вас будет: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +#### Использование `list` + +Вы также можете использовать `list` напрямую вместо `List[str]` (или `list[str]` в Python 3.9+): + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial013.py!} + ``` + +!!! note "Технические детали" + Запомните, что в таком случае, FastAPI не будет проверять содержимое списка. + + Например, для List[int] список будет провалидирован (и задокументирован) на содержание только целочисленных элементов. Но для простого `list` такой проверки не будет. + +## Больше метаданных + +Вы можете добавить больше информации об query-параметре. + +Указанная информация будет включена в генерируемую OpenAPI документацию и использована в пользовательском интерфейсе и внешних инструментах. + +!!! note "Технические детали" + Имейте в виду, что разные инструменты могут иметь разные уровни поддержки OpenAPI. + + Некоторые из них могут не отображать (на данный момент) всю заявленную дополнительную информацию, хотя в большинстве случаев отсутствующая функция уже запланирована к разработке. + +Вы можете указать название query-параметра, используя параметр `title`: + +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial007_an_py310.py!} + ``` + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial007.py!} + ``` + +Добавить описание, используя параметр `description`: + +=== "Python 3.10+" + + ```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.6+" + + ```Python hl_lines="15" + {!> ../../../docs_src/query_params_str_validations/tutorial008_an.py!} + ``` + +=== "Python 3.10+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="12" + {!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="13" + {!> ../../../docs_src/query_params_str_validations/tutorial008.py!} + ``` + +## Псевдонимы параметров + +Представьте, что вы хотите использовать query-параметр с названием `item-query`. + +Например: + +``` +http://127.0.0.1:8000/items/?item-query=foobaritems +``` + +Но `item-query` является невалидным именем переменной в Python. + +Наиболее похожее валидное имя `item_query`. + +Но вам всё равно необходим `item-query`... + +Тогда вы можете объявить `псевдоним`, и этот псевдоним будет использоваться для поиска значения параметра запроса: + +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial009.py!} + ``` + +## Устаревшие параметры + +Предположим, вы больше не хотите использовать какой-либо параметр. + +Вы решили оставить его, потому что клиенты всё ещё им пользуются. Но вы хотите отобразить это в документации как устаревший функционал. + +Тогда для `Query` укажите параметр `deprecated=True`: + +=== "Python 3.10+" + + ```Python hl_lines="19" + {!> ../../../docs_src/query_params_str_validations/tutorial010_an_py310.py!} + ``` + +=== "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+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="17" + {!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="18" + {!> ../../../docs_src/query_params_str_validations/tutorial010.py!} + ``` + +В документации это будет отображено следующим образом: + + + +## Исключить из OpenAPI + +Чтобы исключить query-параметр из генерируемой OpenAPI схемы (а также из системы автоматической генерации документации), укажите в `Query` параметр `include_in_schema=False`: + +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial014_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial014_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial014_an.py!} + ``` + +=== "Python 3.10+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + Рекомендуется использовать версию с `Annotated` если возможно. + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial014.py!} + ``` + +## Резюме + +Вы можете объявлять дополнительные правила валидации и метаданные для ваших параметров запроса. + +Общие метаданные: + +* `alias` +* `title` +* `description` +* `deprecated` +* `include_in_schema` + +Специфичные правила валидации для строк: + +* `min_length` +* `max_length` +* `regex` + +В рассмотренных примерах показано объявление правил валидации для строковых значений `str`. + +В следующих главах вы увидете, как объявлять правила валидации для других типов (например, чисел). diff --git a/docs/ru/docs/tutorial/query-params.md b/docs/ru/docs/tutorial/query-params.md new file mode 100644 index 0000000000..68333ec566 --- /dev/null +++ b/docs/ru/docs/tutorial/query-params.md @@ -0,0 +1,225 @@ +# Query-параметры + +Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры. + +```Python hl_lines="9" +{!../../../docs_src/query_params/tutorial001.py!} +``` + +Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`. + +Например, в этом URL-адресе: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +...параметры запроса такие: + +* `skip`: со значением `0` +* `limit`: со значением `10` + +Будучи частью URL-адреса, они "по умолчанию" являются строками. + +Но когда вы объявляете их с использованием аннотаций (в примере выше, как `int`), они конвертируются в указанный тип данных и проходят проверку на соответствие ему. + +Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам: + +* Поддержка от редактора кода (очевидно) +* "Парсинг" данных +* Проверка на соответствие данных (Валидация) +* Автоматическая документация + +## Значения по умолчанию + +Поскольку query-параметры не являются фиксированной частью пути, они могут быть не обязательными и иметь значения по умолчанию. + +В примере выше значения по умолчанию равны `skip=0` и `limit=10`. + +Таким образом, результат перехода по URL-адресу: + +``` +http://127.0.0.1:8000/items/ +``` + +будет таким же, как если перейти используя параметры по умолчанию: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +Но если вы введёте, например: + +``` +http://127.0.0.1:8000/items/?skip=20 +``` + +Значения параметров в вашей функции будут: + +* `skip=20`: потому что вы установили это в URL-адресе +* `limit=10`: т.к это было значение по умолчанию + +## Необязательные параметры + +Аналогично, вы можете объявлять необязательные query-параметры, установив их значение по умолчанию, равное `None`: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params/tutorial002.py!} + ``` + +В этом случае, параметр `q` будет не обязательным и будет иметь значение `None` по умолчанию. + +!!! Важно + Также обратите внимание, что **FastAPI** достаточно умён чтобы заметить, что параметр `item_id` является path-параметром, а `q` нет, поэтому, это параметр запроса. + +## Преобразование типа параметра запроса + +Вы также можете объявлять параметры с типом `bool`, которые будут преобразованы соответственно: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params/tutorial003.py!} + ``` + +В этом случае, если вы сделаете запрос: + +``` +http://127.0.0.1:8000/items/foo?short=1 +``` + +или + +``` +http://127.0.0.1:8000/items/foo?short=True +``` + +или + +``` +http://127.0.0.1:8000/items/foo?short=true +``` + +или + +``` +http://127.0.0.1:8000/items/foo?short=on +``` + +или + +``` +http://127.0.0.1:8000/items/foo?short=yes +``` + +или в любом другом варианте написания (в верхнем регистре, с заглавной буквой, и т.п), внутри вашей функции параметр `short` будет иметь значение `True` типа данных `bool` . В противном случае - `False`. + + +## Смешивание query-параметров и path-параметров + +Вы можете объявлять несколько query-параметров и path-параметров одновременно,**FastAPI** сам разберётся, что чем является. + +И вы не обязаны объявлять их в каком-либо определенном порядке. + +Они будут обнаружены по именам: + +=== "Python 3.10+" + + ```Python hl_lines="6 8" + {!> ../../../docs_src/query_params/tutorial004_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8 10" + {!> ../../../docs_src/query_params/tutorial004.py!} + ``` + +## Обязательные query-параметры + +Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то это значение не является обязательным. + +Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`. + +Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию: + +```Python hl_lines="6-7" +{!../../../docs_src/query_params/tutorial005.py!} +``` + +Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`. + +Если вы откроете в браузере URL-адрес, например: + +``` +http://127.0.0.1:8000/items/foo-item +``` + +...без добавления обязательного параметра `needy`, вы увидите подобного рода ошибку: + +```JSON +{ + "detail": [ + { + "loc": [ + "query", + "needy" + ], + "msg": "field required", + "type": "value_error.missing" + } + ] +} +``` + +Поскольку `needy` является обязательным параметром, вам необходимо указать его в URL-адресе: + +``` +http://127.0.0.1:8000/items/foo-item?needy=sooooneedy +``` + +...это будет работать: + +```JSON +{ + "item_id": "foo-item", + "needy": "sooooneedy" +} +``` + +Конечно, вы можете определить некоторые параметры как обязательные, некоторые - со значением по умполчанию, а некоторые - полностью необязательные: + +=== "Python 3.10+" + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params/tutorial006_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params/tutorial006.py!} + ``` + +В этом примере, у нас есть 3 параметра запроса: + +* `needy`, обязательный `str`. +* `skip`, типа `int` и со значением по умолчанию `0`. +* `limit`, необязательный `int`. + +!!! подсказка + Вы можете использовать класс `Enum` также, как ранее применяли его с [Path-параметрами](path-params.md#predefined-values){.internal-link target=_blank}. diff --git a/docs/ru/docs/tutorial/response-status-code.md b/docs/ru/docs/tutorial/response-status-code.md new file mode 100644 index 0000000000..b2f9b77048 --- /dev/null +++ b/docs/ru/docs/tutorial/response-status-code.md @@ -0,0 +1,89 @@ +# HTTP коды статуса ответа + +Вы можете задать HTTP код статуса ответа с помощью параметра `status_code` подобно тому, как вы определяете схему ответа в любой из *операций пути*: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* и других. + +```Python hl_lines="6" +{!../../../docs_src/response_status_code/tutorial001.py!} +``` + +!!! note "Примечание" + Обратите внимание, что `status_code` является атрибутом метода-декоратора (`get`, `post` и т.д.), а не *функции-обработчика пути* в отличие от всех остальных параметров и тела запроса. + +Параметр `status_code` принимает число, обозначающее HTTP код статуса ответа. + +!!! info "Информация" + В качестве значения параметра `status_code` также может использоваться `IntEnum`, например, из библиотеки `http.HTTPStatus` в Python. + +Это позволит: + +* Возвращать указанный код статуса в ответе. +* Документировать его как код статуса ответа в OpenAPI схеме (а значит, и в пользовательском интерфейсе): + + + +!!! note "Примечание" + Некоторые коды статуса ответа (см. следующий раздел) указывают на то, что ответ не имеет тела. + + FastAPI знает об этом и создаст документацию OpenAPI, в которой будет указано, что тело ответа отсутствует. + +## Об HTTP кодах статуса ответа + +!!! note "Примечание" + Если вы уже знаете, что представляют собой HTTP коды статуса ответа, можете перейти к следующему разделу. + +В протоколе HTTP числовой код состояния из 3 цифр отправляется как часть ответа. + +У кодов статуса есть названия, чтобы упростить их распознавание, но важны именно числовые значения. + +Кратко о значениях кодов: + +* `1XX` – статус-коды информационного типа. Они редко используются разработчиками напрямую. Ответы с этими кодами не могут иметь тела. +* **`2XX`** – статус-коды, сообщающие об успешной обработке запроса. Они используются чаще всего. + * `200` – это код статуса ответа по умолчанию, который означает, что все прошло "OK". + * Другим примером может быть статус `201`, "Created". Он обычно используется после создания новой записи в базе данных. + * Особый случай – `204`, "No Content". Этот статус ответа используется, когда нет содержимого для возврата клиенту, и поэтому ответ не должен иметь тела. +* **`3XX`** – статус-коды, сообщающие о перенаправлениях. Ответы с этими кодами статуса могут иметь или не иметь тело, за исключением ответов со статусом `304`, "Not Modified", у которых не должно быть тела. +* **`4XX`** – статус-коды, сообщающие о клиентской ошибке. Это ещё одна наиболее часто используемая категория. + * Пример – код `404` для статуса "Not Found". + * Для общих ошибок со стороны клиента можно просто использовать код `400`. +* `5XX` – статус-коды, сообщающие о серверной ошибке. Они почти никогда не используются разработчиками напрямую. Когда что-то идет не так в какой-то части кода вашего приложения или на сервере, он автоматически вернёт один из 5XX кодов. + +!!! tip "Подсказка" + Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с документацией MDN об HTTP кодах статуса ответа. + +## Краткие обозначения для запоминания названий кодов + +Рассмотрим предыдущий пример еще раз: + +```Python hl_lines="6" +{!../../../docs_src/response_status_code/tutorial001.py!} +``` + +`201` – это код статуса "Создано". + +Но вам не обязательно запоминать, что означает каждый из этих кодов. + +Для удобства вы можете использовать переменные из `fastapi.status`. + +```Python hl_lines="1 6" +{!../../../docs_src/response_status_code/tutorial002.py!} +``` + +Они содержат те же числовые значения, но позволяют использовать подсказки редактора для выбора кода статуса: + + + +!!! note "Технические детали" + Вы также можете использовать `from starlette import status` вместо `from fastapi import status`. + + **FastAPI** позволяет использовать как `starlette.status`, так и `fastapi.status` исключительно для удобства разработчиков. Но поставляется fastapi.status непосредственно из Starlette. + +## Изменение кода статуса по умолчанию + +Позже, в [Руководстве для продвинутых пользователей](../advanced/response-change-status-code.md){.internal-link target=_blank}, вы узнаете, как возвращать HTTP коды статуса, отличные от используемого здесь кода статуса по умолчанию. diff --git a/docs/ru/docs/tutorial/schema-extra-example.md b/docs/ru/docs/tutorial/schema-extra-example.md new file mode 100644 index 0000000000..a0363b9ba7 --- /dev/null +++ b/docs/ru/docs/tutorial/schema-extra-example.md @@ -0,0 +1,189 @@ +# Объявление примера запроса данных + +Вы можете объявлять примеры данных, которые ваше приложение может получать. + +Вот несколько способов, как это можно сделать. + +## Pydantic `schema_extra` + +Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в Pydantic документации: Настройка схемы: + +=== "Python 3.10+" + + ```Python hl_lines="13-21" + {!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="15-23" + {!> ../../../docs_src/schema_extra_example/tutorial001.py!} + ``` + +Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API. + +!!! tip Подсказка + Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации. + + Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д. + +## Дополнительные аргументы поля `Field` + +При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию. + +Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля: + +=== "Python 3.10+" + + ```Python hl_lines="2 8-11" + {!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 10-13" + {!> ../../../docs_src/schema_extra_example/tutorial002.py!} + ``` + +!!! warning Внимание + Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации. + +## Использование `example` и `examples` в OpenAPI + +При использовании любой из этих функций: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**. + +### Параметр `Body` с аргументом `example` + +Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`: + +=== "Python 3.10+" + + ```Python hl_lines="22-27" + {!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="22-27" + {!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="23-28" + {!> ../../../docs_src/schema_extra_example/tutorial003_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip Заметка + Рекомендуется использовать версию с `Annotated`, если это возможно. + + ```Python hl_lines="18-23" + {!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip Заметка + Рекомендуется использовать версию с `Annotated`, если это возможно. + + ```Python hl_lines="20-25" + {!> ../../../docs_src/schema_extra_example/tutorial003.py!} + ``` + +### Аргумент "example" в UI документации + +С любым из вышеуказанных методов это будет выглядеть так в `/docs`: + + + +### `Body` с аргументом `examples` + +В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**. + +Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией. + +Каждый конкретный пример типа `dict` в аргументе `examples` может содержать: + +* `summary`: Краткое описание для примера. +* `description`: Полное описание, которое может содержать текст в формате Markdown. +* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`. +* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`. + +=== "Python 3.10+" + + ```Python hl_lines="23-49" + {!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="23-49" + {!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24-50" + {!> ../../../docs_src/schema_extra_example/tutorial004_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip Заметка + Рекомендуется использовать версию с `Annotated`, если это возможно. + + ```Python hl_lines="19-45" + {!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip Заметка + Рекомендуется использовать версию с `Annotated`, если это возможно. + + ```Python hl_lines="21-47" + {!> ../../../docs_src/schema_extra_example/tutorial004.py!} + ``` + +### Аргумент "examples" в UI документации + +С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так: + + + +## Технические Детали + +!!! warning Внимание + Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**. + + Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить. + +Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic. + +И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации. + +Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле `examples`, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`. + +Таким образом, OpenAPI 3.0.3 определяет своё собственное поле `example` для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI). + +Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации. + +Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему). + +Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в определение OpenAPI, к объекту `Parameter Object` (в спецификации). + +И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации). + +С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы. diff --git a/docs/ru/docs/tutorial/static-files.md b/docs/ru/docs/tutorial/static-files.md new file mode 100644 index 0000000000..ec09eb5a3a --- /dev/null +++ b/docs/ru/docs/tutorial/static-files.md @@ -0,0 +1,40 @@ +# Статические Файлы + +Вы можете предоставлять статические файлы автоматически из директории, используя `StaticFiles`. + +## Использование `StaticFiles` + +* Импортируйте `StaticFiles`. +* "Примонтируйте" экземпляр `StaticFiles()` с указанием определенной директории. + +```Python hl_lines="2 6" +{!../../../docs_src/static_files/tutorial001.py!} +``` + +!!! заметка "Технические детали" + Вы также можете использовать `from starlette.staticfiles import StaticFiles`. + + **FastAPI** предоставляет `starlette.staticfiles` под псевдонимом `fastapi.staticfiles`, просто для вашего удобства, как разработчика. Но на самом деле это берётся напрямую из библиотеки Starlette. + +### Что такое "Монтирование" + +"Монтирование" означает добавление полноценного "независимого" приложения в определенную директорию, которое затем обрабатывает все подпути. + +Это отличается от использования `APIRouter`, так как примонтированное приложение является полностью независимым. +OpenAPI и документация из вашего главного приложения не будет содержать ничего из примонтированного приложения, и т.д. + +Вы можете прочитать больше об этом в **Расширенном руководстве пользователя**. + +## Детали + +Первый параметр `"/static"` относится к подпути, по которому это "подприложение" будет "примонтировано". Таким образом, любой путь начинающийся со `"/static"` будет обработан этим приложением. + +Параметр `directory="static"` относится к имени директории, которая содержит ваши статические файлы. + +`name="static"` даёт имя маршруту, которое может быть использовано внутри **FastAPI**. + +Все эти параметры могут отличаться от "`static`", настройте их в соответствии с вашими нуждами и конкретными деталями вашего собственного приложения. + +## Больше информации + +Для получения дополнительной информации о деталях и настройках ознакомьтесь с Документацией Starlette о статических файлах. diff --git a/docs/ru/docs/tutorial/testing.md b/docs/ru/docs/tutorial/testing.md new file mode 100644 index 0000000000..3f90051123 --- /dev/null +++ b/docs/ru/docs/tutorial/testing.md @@ -0,0 +1,212 @@ +# Тестирование + +Благодаря Starlette, тестировать приложения **FastAPI** легко и приятно. + +Тестирование основано на библиотеке HTTPX, которая в свою очередь основана на библиотеке Requests, так что все действия знакомы и интуитивно понятны. + +Используя эти инструменты, Вы можете напрямую задействовать pytest с **FastAPI**. + +## Использование класса `TestClient` + +!!! info "Информация" + Для использования класса `TestClient` необходимо установить библиотеку `httpx`. + + Например, так: `pip install httpx`. + +Импортируйте `TestClient`. + +Создайте объект `TestClient`, передав ему в качестве параметра Ваше приложение **FastAPI**. + +Создайте функцию, название которой должно начинаться с `test_` (это стандарт из соглашений `pytest`). + +Используйте объект `TestClient` так же, как Вы используете `httpx`. + +Напишите простое утверждение с `assert` дабы проверить истинность Python-выражения (это тоже стандарт `pytest`). + +```Python hl_lines="2 12 15-18" +{!../../../docs_src/app_testing/tutorial001.py!} +``` + +!!! tip "Подсказка" + Обратите внимание, что тестирующая функция является обычной `def`, а не асинхронной `async def`. + + И вызов клиента также осуществляется без `await`. + + Это позволяет вам использовать `pytest` без лишних усложнений. + +!!! note "Технические детали" + Также можно написать `from starlette.testclient import TestClient`. + + **FastAPI** предоставляет тот же самый `starlette.testclient` как `fastapi.testclient`. Это всего лишь небольшое удобство для Вас, как разработчика. + +!!! tip "Подсказка" + Если для тестирования Вам, помимо запросов к приложению FastAPI, необходимо вызывать асинхронные функции (например, для подключения к базе данных с помощью асинхронного драйвера), то ознакомьтесь со страницей [Асинхронное тестирование](../advanced/async-tests.md){.internal-link target=_blank} в расширенном руководстве. + +## Разделение тестов и приложения + +В реальном приложении Вы, вероятно, разместите тесты в отдельном файле. + +Кроме того, Ваше приложение **FastAPI** может состоять из нескольких файлов, модулей и т.п. + +### Файл приложения **FastAPI** + +Допустим, структура файлов Вашего приложения похожа на ту, что описана на странице [Более крупные приложения](./bigger-applications.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +Здесь файл `main.py` является "точкой входа" в Ваше приложение и содержит инициализацию Вашего приложения **FastAPI**: + + +```Python +{!../../../docs_src/app_testing/main.py!} +``` + +### Файл тестов + +Также у Вас может быть файл `test_main.py` содержащий тесты. Можно разместить тестовый файл и файл приложения в одной директории (в директориях для Python-кода желательно размещать и файл `__init__.py`): + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Так как оба файла находятся в одной директории, для импорта объекта приложения из файла `main` в файл `test_main` Вы можете использовать относительный импорт: + +```Python hl_lines="3" +{!../../../docs_src/app_testing/test_main.py!} +``` + +...и писать дальше тесты, как и раньше. + +## Тестирование: расширенный пример + +Теперь давайте расширим наш пример и добавим деталей, чтоб посмотреть, как тестировать различные части приложения. + +### Расширенный файл приложения **FastAPI** + +Мы продолжим работу с той же файловой структурой, что и ранее: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Предположим, что в файле `main.py` с приложением **FastAPI** есть несколько **операций пути**. + +В нём описана операция `GET`, которая может вернуть ошибку. + +Ещё есть операция `POST` и она тоже может вернуть ошибку. + +Обе *операции пути* требуют наличия в запросе заголовка `X-Token`. + +=== "Python 3.10+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} + ``` + +=== "Python 3.9+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an/main.py!} + ``` + +=== "Python 3.10+ без Annotated" + + !!! tip "Подсказка" + По возможности используйте версию с `Annotated`. + + ```Python + {!> ../../../docs_src/app_testing/app_b_py310/main.py!} + ``` + +=== "Python 3.6+ без Annotated" + + !!! tip "Подсказка" + По возможности используйте версию с `Annotated`. + + ```Python + {!> ../../../docs_src/app_testing/app_b/main.py!} + ``` + +### Расширенный файл тестов + +Теперь обновим файл `test_main.py`, добавив в него тестов: + +```Python +{!> ../../../docs_src/app_testing/app_b/test_main.py!} +``` + +Если Вы не знаете, как передать информацию в запросе, можете воспользоваться поисковиком (погуглить) и задать вопрос: "Как передать информацию в запросе с помощью `httpx`", можно даже спросить: "Как передать информацию в запросе с помощью `requests`", поскольку дизайн HTTPX основан на дизайне Requests. + +Затем Вы просто применяете найденные ответы в тестах. + +Например: + +* Передаёте *path*-параметры или *query*-параметры, вписав их непосредственно в строку URL. +* Передаёте JSON в теле запроса, передав Python-объект (например: `dict`) через именованный параметр `json`. +* Если же Вам необходимо отправить *форму с данными* вместо JSON, то используйте параметр `data` вместо `json`. +* Для передачи *заголовков*, передайте объект `dict` через параметр `headers`. +* Для передачи *cookies* также передайте `dict`, но через параметр `cookies`. + +Для получения дополнительной информации о передаче данных на бэкенд с помощью `httpx` или `TestClient` ознакомьтесь с документацией HTTPX. + +!!! info "Информация" + Обратите внимание, что `TestClient` принимает данные, которые можно конвертировать в JSON, но не модели Pydantic. + + Если в Ваших тестах есть модели Pydantic и Вы хотите отправить их в тестируемое приложение, то можете использовать функцию `jsonable_encoder`, описанную на странице [Кодировщик совместимый с JSON](encoder.md){.internal-link target=_blank}. + +## Запуск тестов + +Далее Вам нужно установить `pytest`: + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +Он автоматически найдёт все файлы и тесты, выполнит их и предоставит Вам отчёт о результатах тестирования. + +Запустите тесты командой `pytest` и увидите результат: + +
+ +```console +$ pytest + +================ test session starts ================ +platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 +rootdir: /home/user/code/superawesome-cli/app +plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 +collected 6 items + +---> 100% + +test_main.py ...... [100%] + +================= 1 passed in 0.03s ================= +``` + +
diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index bb0702489d..de18856f44 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -1,138 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/ru/ -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: ru -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- async.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/sq/docs/index.md b/docs/sq/docs/index.md deleted file mode 100644 index 0bb7b55e3b..0000000000 --- a/docs/sq/docs/index.md +++ /dev/null @@ -1,466 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - -

- ---- - -**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.6+ 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 -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.6+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install uvicorn[standard] - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```Python -from typing import Optional - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="9 14" -from typing import Optional - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Optional[bool] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on `requests` and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* requests - 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). -* graphene - Required for `GraphQLApp` support. -* 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/sq/mkdocs.yml b/docs/sq/mkdocs.yml deleted file mode 100644 index 8914395fef..0000000000 --- a/docs/sq/mkdocs.yml +++ /dev/null @@ -1,137 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/sq/ -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - 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 -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: /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: /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/tr/docs/features.md b/docs/tr/docs/features.md index 0bcda4e9c7..f8220fb58f 100644 --- a/docs/tr/docs/features.md +++ b/docs/tr/docs/features.md @@ -174,7 +174,7 @@ Bütün entegrasyonlar kullanımı kolay olmak üzere (zorunluluklar ile beraber * **GraphQL** desteği. * Kullanım halinde arka plan işlevleri. * Başlatma ve kapatma eventleri(startup and shutdown). -* Test sunucusu `requests` üzerine kurulu. +* Test sunucusu HTTPX üzerine kurulu. * **CORS**, GZip, Static dosyalar, Streaming responseları. * **Session and Cookie** desteği. * 100% test kapsayıcılığı. diff --git a/docs/tr/docs/index.md b/docs/tr/docs/index.md index 3195cd4409..2339337f34 100644 --- a/docs/tr/docs/index.md +++ b/docs/tr/docs/index.md @@ -119,7 +119,7 @@ Eğer API yerine komut satırı uygulaması ## Gereksinimler -Python 3.6+ +Python 3.7+ FastAPI iki devin omuzları üstünde duruyor: @@ -143,7 +143,7 @@ Uygulamanı kullanılabilir hale getirmek için ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -157,7 +157,7 @@ $ pip install uvicorn[standard] * `main.py` adında bir dosya oluştur : ```Python -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -170,7 +170,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -180,7 +180,7 @@ def read_item(item_id: int, q: Optional[str] = None): Eğer kodunda `async` / `await` var ise, `async def` kullan: ```Python hl_lines="9 14" -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -193,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): +async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` @@ -272,7 +272,7 @@ Senin için alternatif olarak (ujson - daha hızlı JSON "dönüşümü" için. * email_validator - email doğrulaması için. Starlette tarafında kullanılan: -* requests - Eğer `TestClient` kullanmak istiyorsan gerekli. +* httpx - Eğer `TestClient` kullanmak istiyorsan gerekli. * jinja2 - Eğer kendine ait template konfigürasyonu oluşturmak istiyorsan gerekli * python-multipart - Form kullanmak istiyorsan gerekli ("dönüşümü"). * itsdangerous - `SessionMiddleware` desteği için gerekli. diff --git a/docs/tr/docs/tutorial/first_steps.md b/docs/tr/docs/tutorial/first_steps.md new file mode 100644 index 0000000000..b39802f5d6 --- /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: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +!!! note + `uvicorn main:app` komutu şunu ifade eder: + + * `main`: `main.py` dosyası (the Python "module"). + * `app`: `main.py` dosyası içerisinde `app = FastAPI()` satırıyla oluşturulan nesne. + * `--reload`: Kod değişikliği sonrasında sunucunun yeniden başlatılmasını sağlar. Yalnızca geliştirme için kullanın. + +Çıktıda şu şekilde bir satır vardır: + +```hl_lines="4" +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +Bu satır, yerel makinenizde uygulamanızın sunulduğu URL'yi gösterir. + +### Kontrol Et + +Tarayıcınızda http://127.0.0.1:8000 adresini açın. + +Bir JSON yanıtı göreceksiniz: + +```JSON +{"message": "Hello World"} +``` + +### İnteraktif API dokümantasyonu + +http://127.0.0.1:8000/docs adresine gidin. + +Otomatik oluşturulmuş( Swagger UI tarafından sağlanan) interaktif bir API dokümanı göreceksiniz: + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### Alternatif API dokümantasyonu + +Şimdi, http://127.0.0.1:8000/redoc adresine gidin. + +Otomatik oluşturulmuş(ReDoc tarafından sağlanan) bir API dokümanı göreceksiniz: + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +### OpenAPI + +**FastAPI**, **OpenAPI** standardını kullanarak tüm API'lerinizi açıklayan bir "şema" oluşturur. + +#### "Şema" + +Bir "şema", bir şeyin tanımı veya açıklamasıdır. Soyut bir açıklamadır, uygulayan kod değildir. + +#### API "şemaları" + +Bu durumda, OpenAPI, API şemasını nasıl tanımlayacağınızı belirten şartnamelerdir. + +Bu şema tanımı, API yollarınızı, aldıkları olası parametreleri vb. içerir. + +#### Data "şema" + +"Şema" terimi, JSON içeriği gibi bazı verilerin şeklini de ifade edebilir. + +Bu durumda, JSON öznitelikleri ve sahip oldukları veri türleri vb. anlamına gelir. + +#### OpenAPI and JSON Şema + +OpenAPI, API'niz için bir API şeması tanımlar. Ve bu şema, JSON veri şemaları standardı olan **JSON Şema** kullanılarak API'niz tarafından gönderilen ve alınan verilerin tanımlarını (veya "şemalarını") içerir. + +#### `openapi.json` kontrol et + +OpenAPI şemasının nasıl göründüğünü merak ediyorsanız, FastAPI otomatik olarak tüm API'nizin açıklamalarını içeren bir JSON (şema) oluşturur. + +Doğrudan şu adreste görebilirsiniz: http://127.0.0.1:8000/openapi.json. + +Aşağıdaki gibi bir şeyle başlayan bir JSON gösterecektir: + +```JSON +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +#### OpenAPI ne içindir? + +OpenAPI şeması, dahili olarak bulunan iki etkileşimli dokümantasyon sistemine güç veren şeydir. + +Ve tamamen OpenAPI'ye dayalı düzinelerce alternatif vardır. **FastAPI** ile oluşturulmuş uygulamanıza bu alternatiflerden herhangi birini kolayca ekleyebilirsiniz. + +API'nizle iletişim kuran istemciler için otomatik olarak kod oluşturmak için de kullanabilirsiniz. Örneğin, frontend, mobil veya IoT uygulamaları. + +## Adım adım özet + +### Adım 1: `FastAPI`yi içe aktarın + +```Python hl_lines="1" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`FastAPI`, API'niz için tüm fonksiyonları sağlayan bir Python sınıfıdır. + +!!! note "Teknik Detaylar" + `FastAPI` doğrudan `Starlette` kalıtım alan bir sınıftır. + + Tüm Starlette fonksiyonlarını `FastAPI` ile de kullanabilirsiniz. + +### Adım 2: Bir `FastAPI` örneği oluşturun + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Burada `app` değişkeni `FastAPI` sınıfının bir örneği olacaktır. + +Bu tüm API'yi oluşturmak için ana etkileşim noktası olacaktır. + +`uvicorn` komutunda atıfta bulunulan `app` ile aynıdır. + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Uygulamanızı aşağıdaki gibi oluşturursanız: + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial002.py!} +``` + +Ve bunu `main.py` dosyasına koyduktan sonra `uvicorn` komutunu şu şekilde çağırabilirsiniz: + +
+ +```console +$ uvicorn main:my_awesome_api --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Adım 3: *Path işlemleri* oluşturmak + +#### Path + +Burada "Path" URL'de ilk "\" ile başlayan son bölümü ifade eder. + +Yani, şu şekilde bir URL'de: + +``` +https://example.com/items/foo +``` + +... path şöyle olabilir: + +``` +/items/foo +``` + +!!! info + Genellikle bir "path", "endpoint" veya "route" olarak adlandırılabilir. + +Bir API oluştururken, "path", "resource" ile "concern" ayırmanın ana yoludur. + +#### İşlemler + +Burada "işlem" HTTP methodlarından birini ifade eder. + +Onlardan biri: + +* `POST` +* `GET` +* `PUT` +* `DELETE` + +... ve daha egzotik olanları: + +* `OPTIONS` +* `HEAD` +* `PATCH` +* `TRACE` + +HTTP protokolünde, bu "methodlardan" birini (veya daha fazlasını) kullanarak her path ile iletişim kurabilirsiniz. + +--- + +API'lerinizi oluştururkan, belirli bir işlemi gerçekleştirirken belirli HTTP methodlarını kullanırsınız. + +Normalde kullanılan: + +* `POST`: veri oluşturmak. +* `GET`: veri okumak. +* `PUT`: veriyi güncellemek. +* `DELETE`: veriyi silmek. + +Bu nedenle, OpenAPI'de HTTP methodlarından her birine "işlem" denir. + +Bizde onlara "**işlemler**" diyeceğiz. + +#### Bir *Path işlem decoratorleri* tanımlanmak + +```Python hl_lines="6" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`@app.get("/")` **FastAPI'ye** aşağıdaki fonksiyonun adresine giden istekleri işlemekten sorumlu olduğunu söyler: + +* path `/` +* 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 74186033c6..de18856f44 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -1,140 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/tr/ -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: tr -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md deleted file mode 100644 index 0bb7b55e3b..0000000000 --- a/docs/uk/docs/index.md +++ /dev/null @@ -1,466 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - -

- ---- - -**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.6+ 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 -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.6+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install uvicorn[standard] - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```Python -from typing import Optional - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="9 14" -from typing import Optional - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Optional[bool] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on `requests` and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* requests - 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). -* graphene - Required for `GraphQLApp` support. -* 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/uk/mkdocs.yml b/docs/uk/mkdocs.yml deleted file mode 100644 index ddf299d8b6..0000000000 --- a/docs/uk/mkdocs.yml +++ /dev/null @@ -1,137 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/uk/ -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: uk -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - 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 -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: /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: /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/uk/overrides/.gitignore b/docs/uk/overrides/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/zh/docs/advanced/additional-status-codes.md b/docs/zh/docs/advanced/additional-status-codes.md index 40cceebee9..54ec9775b6 100644 --- a/docs/zh/docs/advanced/additional-status-codes.md +++ b/docs/zh/docs/advanced/additional-status-codes.md @@ -14,7 +14,7 @@ 要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为为你要的值。 -```Python hl_lines="2 19" +```Python hl_lines="4 25" {!../../../docs_src/additional_status_codes/tutorial001.py!} ``` diff --git a/docs/zh/docs/advanced/index.md b/docs/zh/docs/advanced/index.md index d71838cd77..824f91f47b 100644 --- a/docs/zh/docs/advanced/index.md +++ b/docs/zh/docs/advanced/index.md @@ -1,4 +1,4 @@ -# 高级用户指南 - 简介 +# 高级用户指南 ## 额外特性 diff --git a/docs/zh/docs/advanced/response-change-status-code.md b/docs/zh/docs/advanced/response-change-status-code.md new file mode 100644 index 0000000000..a289cf2017 --- /dev/null +++ b/docs/zh/docs/advanced/response-change-status-code.md @@ -0,0 +1,31 @@ +# 响应 - 更改状态码 + +你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。 + +但在某些情况下,你需要返回一个不同于默认值的状态码。 + +## 使用场景 + +例如,假设你想默认返回一个HTTP状态码为“OK”`200`。 + +但如果数据不存在,你想创建它,并返回一个HTTP状态码为“CREATED”`201`。 + +但你仍然希望能够使用`response_model`过滤和转换你返回的数据。 + +对于这些情况,你可以使用一个`Response`参数。 + +## 使用 `Response` 参数 + +你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。 + +然后你可以在这个*临时*响应对象中设置`status_code`。 + +```Python hl_lines="1 9 12" +{!../../../docs_src/response_change_status_code/tutorial001.py!} +``` + +然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 + +**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 + +你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。 diff --git a/docs/zh/docs/advanced/response-cookies.md b/docs/zh/docs/advanced/response-cookies.md new file mode 100644 index 0000000000..3e53c53191 --- /dev/null +++ b/docs/zh/docs/advanced/response-cookies.md @@ -0,0 +1,47 @@ +# 响应Cookies + +## 使用 `Response` 参数 + +你可以在 *路径函数* 中定义一个类型为 `Response`的参数,这样你就可以在这个临时响应对象中设置cookie了。 + +```Python hl_lines="1 8-9" +{!../../../docs_src/response_cookies/tutorial002.py!} +``` + +而且你还可以根据你的需要响应不同的对象,比如常用的 `dict`,数据库model等。 + +如果你定义了 `response_model`,程序会自动根据`response_model`来过滤和转换你响应的对象。 + +**FastAPI** 会使用这个 *临时* 响应对象去装在这些cookies信息 (同样还有headers和状态码等信息), 最终会将这些信息和通过`response_model`转化过的数据合并到最终的响应里。 + +你也可以在depend中定义`Response`参数,并设置cookie和header。 + +## 直接响应 `Response` + +你还可以在直接响应`Response`时直接创建cookies。 + +你可以参考[Return a Response Directly](response-directly.md){.internal-link target=_blank}来创建response + +然后设置Cookies,并返回: + +```Python hl_lines="10-12" +{!../../../docs_src/response_cookies/tutorial001.py!} +``` + +!!! tip + 需要注意,如果你直接反馈一个response对象,而不是使用`Response`入参,FastAPI则会直接反馈你封装的response对象。 + + 所以你需要确保你响应数据类型的正确性,如:你可以使用`JSONResponse`来兼容JSON的场景。 + + 同时,你也应当仅反馈通过`response_model`过滤过的数据。 + +### 更多信息 + +!!! note "技术细节" + 你也可以使用`from starlette.responses import Response` 或者 `from starlette.responses import JSONResponse`。 + + 为了方便开发者,**FastAPI** 封装了相同数据类型,如`starlette.responses` 和 `fastapi.responses`。不过大部分response对象都是直接引用自Starlette。 + + 因为`Response`对象可以非常便捷的设置headers和cookies,所以 **FastAPI** 同时也封装了`fastapi.Response`。 + +如果你想查看所有可用的参数和选项,可以参考 Starlette帮助文档 diff --git a/docs/zh/docs/advanced/response-headers.md b/docs/zh/docs/advanced/response-headers.md new file mode 100644 index 0000000000..85dab15ac0 --- /dev/null +++ b/docs/zh/docs/advanced/response-headers.md @@ -0,0 +1,39 @@ +# 响应头 + +## 使用 `Response` 参数 + +你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。 + +然后你可以在这个*临时*响应对象中设置头部。 +```Python hl_lines="1 7-8" +{!../../../docs_src/response_headers/tutorial002.py!} +``` + +然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 + +**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 + +你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。 + +## 直接返回 `Response` + +你也可以在直接返回`Response`时添加头部。 + +按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: +```Python hl_lines="10-12" +{!../../../docs_src/response_headers/tutorial001.py!} +``` + + +!!! 注意 "技术细节" + 你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。 + + **FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。 + + 由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。 + +## 自定义头部 + +请注意,可以使用'X-'前缀添加自定义专有头部。 + +但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在Starlette的CORS文档中记录的`expose_headers`参数。 diff --git a/docs/zh/docs/advanced/security/index.md b/docs/zh/docs/advanced/security/index.md new file mode 100644 index 0000000000..fdc8075c76 --- /dev/null +++ b/docs/zh/docs/advanced/security/index.md @@ -0,0 +1,16 @@ +# 高级安全 + +## 附加特性 + +除 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性. + +!!! tip "小贴士" + 接下来的章节 **并不一定是 "高级的"**. + + 而且对于你的使用场景来说,解决方案很可能就在其中。 + +## 先阅读教程 + +接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank}. + +它们都基于相同的概念,但支持一些额外的功能. diff --git a/docs/zh/docs/advanced/settings.md b/docs/zh/docs/advanced/settings.md new file mode 100644 index 0000000000..597e99a779 --- /dev/null +++ b/docs/zh/docs/advanced/settings.md @@ -0,0 +1,433 @@ +# 设置和环境变量 + +在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。 + +这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。 + +因此,通常会将它们提供为由应用程序读取的环境变量。 + +## 环境变量 + +!!! tip + 如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。 + +环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。 + +您可以在 shell 中创建和使用环境变量,而无需使用 Python: + +=== "Linux、macOS、Windows Bash" + +
+ + ```console + // 您可以创建一个名为 MY_NAME 的环境变量 + $ export MY_NAME="Wade Wilson" + + // 然后您可以与其他程序一起使用它,例如 + $ echo "Hello $MY_NAME" + + Hello Wade Wilson + ``` + +
+ +=== "Windows PowerShell" + +
+ + ```console + // 创建一个名为 MY_NAME 的环境变量 + $ $Env:MY_NAME = "Wade Wilson" + + // 与其他程序一起使用它,例如 + $ echo "Hello $Env:MY_NAME" + + Hello Wade Wilson + ``` + +
+ +### 在 Python 中读取环境变量 + +您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。 + +例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +!!! tip + `os.getenv()` 的第二个参数是要返回的默认值。 + + 如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。 + +然后,您可以调用该 Python 程序: + +
+ +```console +// 这里我们还没有设置环境变量 +$ python main.py + +// 因为我们没有设置环境变量,所以我们得到默认值 + +Hello World from Python + +// 但是如果我们先创建一个环境变量 +$ export MY_NAME="Wade Wilson" + +// 然后再次调用程序 +$ python main.py + +// 现在它可以读取环境变量 + +Hello Wade Wilson from Python +``` + +
+ +由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。 + + + +您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。 + +要做到这一点,在程序本身之前的同一行创建它: + +
+ +```console +// 在此程序调用行中创建一个名为 MY_NAME 的环境变量 +$ MY_NAME="Wade Wilson" python main.py + +// 现在它可以读取环境变量 + +Hello Wade Wilson from Python + +// 之后环境变量不再存在 +$ python main.py + +Hello World from Python +``` + +
+ +!!! tip + 您可以在 Twelve-Factor App: Config 中阅读更多相关信息。 + +### 类型和验证 + +这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。 + +这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。 + +## Pydantic 的 `Settings` + +幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即Pydantic: Settings management。 + +### 创建 `Settings` 对象 + +从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。 + +与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。 + +您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。 + +```Python hl_lines="2 5-8 11" +{!../../../docs_src/settings/tutorial001.py!} +``` + +!!! tip + 如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。 + +然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。 + +然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。 + +### 使用 `settings` + +然后,您可以在应用程序中使用新的 `settings` 对象: + +```Python hl_lines="18-20" +{!../../../docs_src/settings/tutorial001.py!} +``` + +### 运行服务器 + +接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +!!! tip + 要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。 + +然后,`admin_email` 设置将为 `"deadpool@example.com"`。 + +`app_name` 将为 `"ChimichangApp"`。 + +而 `items_per_user` 将保持其默认值为 `50`。 + +## 在另一个模块中设置 + +您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。 + +例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容: + +```Python +{!../../../docs_src/settings/app01/config.py!} +``` + +然后在一个名为 `main.py` 的文件中使用它: + +```Python hl_lines="3 11-13" +{!../../../docs_src/settings/app01/main.py!} +``` +!!! tip + 您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 + +## 在依赖项中使用设置 + +在某些情况下,从依赖项中提供设置可能比在所有地方都使用全局对象 `settings` 更有用。 + +这在测试期间尤其有用,因为很容易用自定义设置覆盖依赖项。 + +### 配置文件 + +根据前面的示例,您的 `config.py` 文件可能如下所示: + +```Python hl_lines="10" +{!../../../docs_src/settings/app02/config.py!} +``` + +请注意,现在我们不创建默认实例 `settings = Settings()`。 + +### 主应用程序文件 + +现在我们创建一个依赖项,返回一个新的 `config.Settings()`。 + +=== "Python 3.9+" + + ```Python hl_lines="6 12-13" + {!> ../../../docs_src/settings/app02_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="6 12-13" + {!> ../../../docs_src/settings/app02_an/main.py!} + ``` + +=== "Python 3.6+ 非注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="5 11-12" + {!> ../../../docs_src/settings/app02/main.py!} + ``` + +!!! tip + 我们稍后会讨论 `@lru_cache()`。 + + 目前,您可以将 `get_settings()` 视为普通函数。 + +然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。 + +=== "Python 3.9+" + + ```Python hl_lines="17 19-21" + {!> ../../../docs_src/settings/app02_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17 19-21" + {!> ../../../docs_src/settings/app02_an/main.py!} + ``` + +=== "Python 3.6+ 非注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="16 18-20" + {!> ../../../docs_src/settings/app02/main.py!} + ``` + +### 设置和测试 + +然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象: + +```Python hl_lines="9-10 13 21" +{!../../../docs_src/settings/app02/test_main.py!} +``` + +在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。 + +然后,我们可以测试它是否被使用。 + +## 从 `.env` 文件中读取设置 + +如果您有许多可能经常更改的设置,可能在不同的环境中,将它们放在一个文件中,然后从该文件中读取它们,就像它们是环境变量一样,可能非常有用。 + +这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。 + +!!! tip + 以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。 + + 但是,dotenv 文件实际上不一定要具有确切的文件名。 + +Pydantic 支持使用外部库从这些类型的文件中读取。您可以在Pydantic 设置: Dotenv (.env) 支持中阅读更多相关信息。 + +!!! tip + 要使其工作,您需要执行 `pip install python-dotenv`。 + +### `.env` 文件 + +您可以使用以下内容创建一个名为 `.env` 的文件: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### 从 `.env` 文件中读取设置 + +然后,您可以使用以下方式更新您的 `config.py`: + +```Python hl_lines="9-10" +{!../../../docs_src/settings/app03/config.py!} +``` + +在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。 + +!!! tip + `Config` 类仅用于 Pydantic 配置。您可以在Pydantic Model Config中阅读更多相关信息。 + +### 使用 `lru_cache` 仅创建一次 `Settings` + +从磁盘中读取文件通常是一项耗时的(慢)操作,因此您可能希望仅在首次读取后并重复使用相同的设置对象,而不是为每个请求都读取它。 + +但是,每次执行以下操作: + +```Python +Settings() +``` + +都会创建一个新的 `Settings` 对象,并且在创建时会再次读取 `.env` 文件。 + +如果依赖项函数只是这样的: + +```Python +def get_settings(): + return Settings() +``` + +我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️ + +但是,由于我们在顶部使用了 `@lru_cache()` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️ + +=== "Python 3.9+" + + ```Python hl_lines="1 11" + {!> ../../../docs_src/settings/app03_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 11" + {!> ../../../docs_src/settings/app03_an/main.py!} + ``` + +=== "Python 3.6+ 非注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="1 10" + {!> ../../../docs_src/settings/app03/main.py!} + ``` + +然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。 + +#### `lru_cache` 技术细节 + +`@lru_cache()` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。 + +因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。 + +例如,如果您有一个函数: +```Python +@lru_cache() +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +您的程序可以像这样执行: + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: 执行函数代码 + execute ->> code: 返回结果 + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: 返回存储的结果 + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: 执行函数代码 + execute ->> code: 返回结果 + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: 执行函数代码 + execute ->> code: 返回结果 + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: 返回存储的结果 + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: 返回存储的结果 + end +``` + +对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。 + +这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。 + +`@lru_cache()` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在Python 文档中了解有关 `@lru_cache()` 的更多信息。 + +## 小结 + +您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。 + +* 通过使用依赖项,您可以简化测试。 +* 您可以使用 `.env` 文件。 +* 使用 `@lru_cache()` 可以避免为每个请求重复读取 dotenv 文件,同时允许您在测试时进行覆盖。 diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md new file mode 100644 index 0000000000..a723487fdf --- /dev/null +++ b/docs/zh/docs/advanced/websockets.md @@ -0,0 +1,214 @@ +# WebSockets + +您可以在 **FastAPI** 中使用 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)。 + +## 安装 `WebSockets` + +首先,您需要安装 `WebSockets`: + +```console +$ pip install websockets + +---> 100% +``` + +## WebSockets 客户端 + +### 在生产环境中 + +在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。 + +要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。 + +或者,您可能有一个原生移动应用程序,直接使用原生代码与 WebSocket 后端通信。 + +或者,您可能有其他与 WebSocket 终端通信的方式。 + +--- + +但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。 + +当然,这并不是最优的做法,您不应该在生产环境中使用它。 + +在生产环境中,您应该选择上述任一选项。 + +但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: + +```Python hl_lines="2 6-38 41-43" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +## 创建 `websocket` + +在您的 **FastAPI** 应用程序中,创建一个 `websocket`: + +```Python hl_lines="1 46-47" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +!!! note "技术细节" + 您也可以使用 `from starlette.websockets import WebSocket`。 + + **FastAPI** 直接提供了相同的 `WebSocket`,只是为了方便开发人员。但它直接来自 Starlette。 + +## 等待消息并发送消息 + +在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 + +```Python hl_lines="48-52" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +您可以接收和发送二进制、文本和 JSON 数据。 + +## 尝试一下 + +如果您的文件名为 `main.py`,请使用以下命令运行应用程序: + +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +在浏览器中打开 http://127.0.0.1:8000。 + +您将看到一个简单的页面,如下所示: + + + +您可以在输入框中输入消息并发送: + + + +您的 **FastAPI** 应用程序将回复: + + + +您可以发送(和接收)多条消息: + + + +所有这些消息都将使用同一个 WebSocket 连 + +接。 + +## 使用 `Depends` 和其他依赖项 + +在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同: + +=== "Python 3.10+" + + ```Python hl_lines="68-69 82" + {!> ../../../docs_src/websockets/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="68-69 82" + {!> ../../../docs_src/websockets/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="69-70 83" + {!> ../../../docs_src/websockets/tutorial002_an.py!} + ``` + +=== "Python 3.10+ 非带注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="66-67 79" + {!> ../../../docs_src/websockets/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ 非带注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="68-69 81" + {!> ../../../docs_src/websockets/tutorial002.py!} + ``` + +!!! info + 由于这是一个 WebSocket,抛出 `HTTPException` 并不是很合理,而是抛出 `WebSocketException`。 + + 您可以使用规范中定义的有效代码。 + +### 尝试带有依赖项的 WebSockets + +如果您的文件名为 `main.py`,请使用以下命令运行应用程序: + +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +在浏览器中打开 http://127.0.0.1:8000。 + +在页面中,您可以设置: + +* "Item ID",用于路径。 +* "Token",作为查询参数。 + +!!! tip + 注意,查询参数 `token` 将由依赖项处理。 + +通过这样,您可以连接 WebSocket,然后发送和接收消息: + + + +## 处理断开连接和多个客户端 + +当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 + +=== "Python 3.9+" + + ```Python hl_lines="79-81" + {!> ../../../docs_src/websockets/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="81-83" + {!> ../../../docs_src/websockets/tutorial003.py!} + ``` + +尝试以下操作: + +* 使用多个浏览器选项卡打开应用程序。 +* 从这些选项卡中发送消息。 +* 然后关闭其中一个选项卡。 + +这将引发 `WebSocketDisconnect` 异常,并且所有其他客户端都会收到类似以下的消息: + +``` +Client #1596980209979 left the chat +``` + +!!! tip + 上面的应用程序是一个最小和简单的示例,用于演示如何处理和向多个 WebSocket 连接广播消息。 + + 但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。 + + 如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。 + +## 更多信息 + +要了解更多选项,请查看 Starlette 的文档: + +* [WebSocket 类](https://www.starlette.io/websockets/) +* [基于类的 WebSocket 处理](https://www.starlette.io/endpoints/#websocketendpoint)。 diff --git a/docs/zh/docs/advanced/wsgi.md b/docs/zh/docs/advanced/wsgi.md new file mode 100644 index 0000000000..ad71280fc6 --- /dev/null +++ b/docs/zh/docs/advanced/wsgi.md @@ -0,0 +1,37 @@ +# 包含 WSGI - Flask,Django,其它 + +您可以挂载多个 WSGI 应用,正如您在 [Sub Applications - Mounts](./sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](./behind-a-proxy.md){.internal-link target=_blank} 中所看到的那样。 + +为此, 您可以使用 `WSGIMiddleware` 来包装你的 WSGI 应用,如:Flask,Django,等等。 + +## 使用 `WSGIMiddleware` + +您需要导入 `WSGIMiddleware`。 + +然后使用该中间件包装 WSGI 应用(例如 Flask)。 + +之后将其挂载到某一个路径下。 + +```Python hl_lines="2-3 22" +{!../../../docs_src/wsgi/tutorial001.py!} +``` + +## 检查 + +现在,所有定义在 `/v1/` 路径下的请求将会被 Flask 应用处理。 + +其余的请求则会被 **FastAPI** 处理。 + +如果您使用 Uvicorn 运行应用实例并且访问 http://localhost:8000/v1/,您将会看到由 Flask 返回的响应: + +```txt +Hello, World from Flask! +``` + +并且如果您访问 http://localhost:8000/v2,您将会看到由 FastAPI 返回的响应: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/zh/docs/benchmarks.md b/docs/zh/docs/benchmarks.md index 8991c72cd9..71e8d48382 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/contributing.md b/docs/zh/docs/contributing.md index 95500d12be..4ebd673150 100644 --- a/docs/zh/docs/contributing.md +++ b/docs/zh/docs/contributing.md @@ -88,61 +88,29 @@ $ python -m venv env !!! tip 每一次你在该环境下使用 `pip` 安装了新软件包时,请再次激活该环境。 - 这样可以确保你在使用由该软件包安装的终端程序(如 `flit`)时使用的是当前虚拟环境中的程序,而不是其他的可能是全局安装的程序。 + 这样可以确保你在使用由该软件包安装的终端程序时使用的是当前虚拟环境中的程序,而不是其他的可能是全局安装的程序。 -### Flit +### pip -**FastAPI** 使用 Flit 来构建、打包和发布项目。 - -如上所述激活环境后,安装 `flit`: +如上所述激活环境后:
```console -$ pip install flit +$ pip install -r requirements.txt ---> 100% ```
-现在重新激活环境,以确保你正在使用的是刚刚安装的 `flit`(而不是全局环境的)。 - -然后使用 `flit` 来安装开发依赖: - -=== "Linux, macOS" - -
- - ```console - $ flit install --deps develop --symlink - - ---> 100% - ``` - -
- -=== "Windows" - - If you are on Windows, use `--pth-file` instead of `--symlink`: - -
- - ```console - $ flit install --deps develop --pth-file - - ---> 100% - ``` - -
- 这将在虚拟环境中安装所有依赖和本地版本的 FastAPI。 #### 使用本地 FastAPI 如果你创建一个导入并使用 FastAPI 的 Python 文件,然后使用虚拟环境中的 Python 运行它,它将使用你本地的 FastAPI 源码。 -并且如果你更改该本地 FastAPI 的源码,由于它是通过 `--symlink` (或 Windows 上的 `--pth-file`)安装的,当你再次运行那个 Python 文件,它将使用你刚刚编辑过的最新版本的 FastAPI。 +并且如果你更改该本地 FastAPI 的源码,由于它是通过 `-e` 安装的,当你再次运行那个 Python 文件,它将使用你刚刚编辑过的最新版本的 FastAPI。 这样,你不必再去重新"安装"你的本地版本即可测试所有更改。 @@ -160,7 +128,7 @@ $ bash scripts/format.sh 它还会自动对所有导入代码进行整理。 -为了使整理正确进行,你需要在当前环境中安装本地的 FastAPI,即在运行上述段落中的命令时添加 `--symlink`(或 Windows 上的 `--pth-file`)。 +为了使整理正确进行,你需要在当前环境中安装本地的 FastAPI,即在运行上述段落中的命令时添加 `-e`。 ### 格式化导入 diff --git a/docs/zh/docs/features.md b/docs/zh/docs/features.md index fefe4b1973..2db7f852a9 100644 --- a/docs/zh/docs/features.md +++ b/docs/zh/docs/features.md @@ -171,7 +171,7 @@ FastAPI 有一个使用非常简单,但是非常强大的. diff --git a/docs/zh/docs/tutorial/testing.md b/docs/zh/docs/tutorial/testing.md new file mode 100644 index 0000000000..41f01f8d84 --- /dev/null +++ b/docs/zh/docs/tutorial/testing.md @@ -0,0 +1,212 @@ +# 测试 + +感谢 Starlette,测试**FastAPI** 应用轻松又愉快。 + +它基于 HTTPX, 而HTTPX又是基于Requests设计的,所以很相似且易懂。 + +有了它,你可以直接与**FastAPI**一起使用 pytest。 + +## 使用 `TestClient` + +!!! 信息 + 要使用 `TestClient`,先要安装 `httpx`. + + 例:`pip install httpx`. + +导入 `TestClient`. + +通过传入你的**FastAPI**应用创建一个 `TestClient` 。 + +创建名字以 `test_` 开头的函数(这是标准的 `pytest` 约定)。 + +像使用 `httpx` 那样使用 `TestClient` 对象。 + +为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。 + +```Python hl_lines="2 12 15-18" +{!../../../docs_src/app_testing/tutorial001.py!} +``` + +!!! 提示 + 注意测试函数是普通的 `def`,不是 `async def`。 + + 还有client的调用也是普通的调用,不是用 `await`。 + + 这让你可以直接使用 `pytest` 而不会遇到麻烦。 + +!!! note "技术细节" + 你也可以用 `from starlette.testclient import TestClient`。 + + **FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。但它直接来自Starlette。 + +!!! 提示 + 除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。 + +## 分离测试 + +在实际应用中,你可能会把你的测试放在另一个文件里。 + +您的**FastAPI**应用程序也可能由一些文件/模块组成等等。 + +### **FastAPI** app 文件 + +假设你有一个像 [更大的应用](./bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构: + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +在 `main.py` 文件中你有一个 **FastAPI** app: + + +```Python +{!../../../docs_src/app_testing/main.py!} +``` + +### 测试文件 + +然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件): + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象: + +```Python hl_lines="3" +{!../../../docs_src/app_testing/test_main.py!} +``` + +...然后测试代码和之前一样的。 + +## 测试:扩展示例 + +现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。 + +### 扩展后的 **FastAPI** app 文件 + +让我们继续之前的文件结构: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +假设现在包含**FastAPI** app的文件 `main.py` 有些其他**路径操作**。 + +有个 `GET` 操作会返回错误。 + +有个 `POST` 操作会返回一些错误。 + +所有*路径操作* 都需要一个`X-Token` 头。 + +=== "Python 3.10+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} + ``` + +=== "Python 3.9+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an/main.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + {!> ../../../docs_src/app_testing/app_b_py310/main.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + {!> ../../../docs_src/app_testing/app_b/main.py!} + ``` + +### 扩展后的测试文件 + +然后您可以使用扩展后的测试更新`test_main.py`: + +```Python +{!> ../../../docs_src/app_testing/app_b/test_main.py!} +``` + +每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。 + +接着只需在测试中同样操作。 + +示例: + +* 传一个*路径* 或*查询* 参数,添加到URL上。 +* 传一个JSON体,传一个Python对象(例如一个`dict`)到参数 `json`。 +* 如果你需要发送 *Form Data* 而不是 JSON,使用 `data` 参数。 +* 要发送 *headers*,传 `dict` 给 `headers` 参数。 +* 对于 *cookies*,传 `dict` 给 `cookies` 参数。 + +关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 HTTPX 文档. + +!!! 信息 + 注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。 + + 如果你在测试中有一个Pydantic模型,并且你想在测试时发送它的数据给应用,你可以使用在[JSON Compatible Encoder](encoder.md){.internal-link target=_blank}介绍的`jsonable_encoder` 。 + +## 运行起来 + +之后,你只需要安装 `pytest`: + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +他会自动检测文件和测试,执行测试,然后向你报告结果。 + +执行测试: + +
+ +```console +$ pytest + +================ test session starts ================ +platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 +rootdir: /home/user/code/superawesome-cli/app +plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 +collected 6 items + +---> 100% + +test_main.py ...... [100%] + +================= 1 passed in 0.03s ================= +``` + +
diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index a72ecb6576..de18856f44 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -1,188 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/zh/ -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: zh -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/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -- 教程 - 用户指南: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/body.md - - tutorial/query-params-str-validations.md - - tutorial/path-params-numeric-validations.md - - tutorial/body-multiple-params.md - - tutorial/body-fields.md - - tutorial/middleware.md - - tutorial/body-nested-models.md - - tutorial/header-params.md - - tutorial/response-model.md - - tutorial/extra-models.md - - tutorial/response-status-code.md - - tutorial/schema-extra-example.md - - tutorial/extra-data-types.md - - tutorial/cookie-params.md - - tutorial/request-forms.md - - tutorial/request-files.md - - tutorial/request-forms-and-files.md - - tutorial/handling-errors.md - - tutorial/path-operation-configuration.md - - tutorial/body-updates.md - - 依赖项: - - tutorial/dependencies/index.md - - tutorial/dependencies/sub-dependencies.md - - tutorial/dependencies/dependencies-in-path-operation-decorators.md - - tutorial/dependencies/global-dependencies.md - - 安全性: - - tutorial/security/index.md - - tutorial/security/get-current-user.md - - tutorial/security/simple-oauth2.md - - tutorial/security/oauth2-jwt.md - - tutorial/cors.md - - tutorial/bigger-applications.md - - tutorial/metadata.md - - tutorial/debugging.md -- 高级用户指南: - - advanced/index.md - - advanced/path-operation-advanced-configuration.md - - advanced/additional-status-codes.md - - advanced/response-directly.md - - advanced/custom-response.md -- contributing.md -- help-fastapi.md -- benchmarks.md -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 -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: /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: /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 +INHERIT: ../en/mkdocs.yml diff --git a/docs/zh/overrides/.gitignore b/docs/zh/overrides/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs_src/additional_responses/tutorial001.py b/docs_src/additional_responses/tutorial001.py index 79dcc2efee..ffa821b910 100644 --- a/docs_src/additional_responses/tutorial001.py +++ b/docs_src/additional_responses/tutorial001.py @@ -19,5 +19,4 @@ app = FastAPI() async def read_item(item_id: str): if item_id == "foo": return {"id": "foo", "value": "there goes my hero"} - else: - return JSONResponse(status_code=404, content={"message": "Item not found"}) + return JSONResponse(status_code=404, content={"message": "Item not found"}) diff --git a/docs_src/additional_responses/tutorial002.py b/docs_src/additional_responses/tutorial002.py index a46e959596..bd0c957049 100644 --- a/docs_src/additional_responses/tutorial002.py +++ b/docs_src/additional_responses/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from fastapi.responses import FileResponse @@ -23,7 +23,7 @@ app = FastAPI() } }, ) -async def read_item(item_id: str, img: Optional[bool] = None): +async def read_item(item_id: str, img: Union[bool, None] = None): if img: return FileResponse("image.png", media_type="image/png") else: diff --git a/docs_src/additional_responses/tutorial004.py b/docs_src/additional_responses/tutorial004.py index 361aecb8e7..978bc18c13 100644 --- a/docs_src/additional_responses/tutorial004.py +++ b/docs_src/additional_responses/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from fastapi.responses import FileResponse @@ -25,7 +25,7 @@ app = FastAPI() response_model=Item, responses={**responses, 200: {"content": {"image/png": {}}}}, ) -async def read_item(item_id: str, img: Optional[bool] = None): +async def read_item(item_id: str, img: Union[bool, None] = None): if img: return FileResponse("image.png", media_type="image/png") else: diff --git a/docs_src/additional_status_codes/tutorial001.py b/docs_src/additional_status_codes/tutorial001.py index ae101e0a09..74a986a6a9 100644 --- a/docs_src/additional_status_codes/tutorial001.py +++ b/docs_src/additional_status_codes/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Body, FastAPI, status from fastapi.responses import JSONResponse @@ -10,7 +10,9 @@ items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "siz @app.put("/items/{item_id}") async def upsert_item( - item_id: str, name: Optional[str] = Body(None), size: Optional[int] = Body(None) + item_id: str, + name: Union[str, None] = Body(default=None), + size: Union[int, None] = Body(default=None), ): if item_id in items: item = items[item_id] 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 0000000000..b5ad6a16b6 --- /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 0000000000..1865074a11 --- /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 0000000000..89653dd8a0 --- /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 0000000000..38bfa455b8 --- /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/main.py b/docs_src/app_testing/app_b/main.py index df43db8060..11558b8e81 100644 --- a/docs_src/app_testing/app_b/main.py +++ b/docs_src/app_testing/app_b/main.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Header, HTTPException from pydantic import BaseModel @@ -16,11 +16,11 @@ app = FastAPI() class Item(BaseModel): id: str title: str - description: Optional[str] = None + description: Union[str, None] = None @app.get("/items/{item_id}", response_model=Item) -async def read_main(item_id: str, x_token: str = Header(...)): +async def read_main(item_id: str, x_token: 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: @@ -29,7 +29,7 @@ async def read_main(item_id: str, x_token: str = Header(...)): @app.post("/items/", response_model=Item) -async def create_item(item: Item, x_token: str = Header(...)): +async def create_item(item: Item, x_token: str = Header()): if x_token != fake_secret_token: raise HTTPException(status_code=400, detail="Invalid X-Token header") if item.id in fake_db: diff --git a/docs/de/overrides/.gitignore b/docs_src/app_testing/app_b_an/__init__.py similarity index 100% rename from docs/de/overrides/.gitignore rename to docs_src/app_testing/app_b_an/__init__.py 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 0000000000..c63134fc9b --- /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 0000000000..d186b8ecba --- /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/es/overrides/.gitignore b/docs_src/app_testing/app_b_an_py310/__init__.py similarity index 100% rename from docs/es/overrides/.gitignore rename to docs_src/app_testing/app_b_an_py310/__init__.py 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 0000000000..48c27a0b88 --- /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 0000000000..d186b8ecba --- /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/fa/overrides/.gitignore b/docs_src/app_testing/app_b_an_py39/__init__.py similarity index 100% rename from docs/fa/overrides/.gitignore rename to docs_src/app_testing/app_b_an_py39/__init__.py 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 0000000000..935a510b74 --- /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 0000000000..d186b8ecba --- /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/app_b_py310/main.py b/docs_src/app_testing/app_b_py310/main.py index d44ab9e7c4..b4c72de5c9 100644 --- a/docs_src/app_testing/app_b_py310/main.py +++ b/docs_src/app_testing/app_b_py310/main.py @@ -18,7 +18,7 @@ class Item(BaseModel): @app.get("/items/{item_id}", response_model=Item) -async def read_main(item_id: str, x_token: str = Header(...)): +async def read_main(item_id: str, x_token: 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: @@ -27,7 +27,7 @@ async def read_main(item_id: str, x_token: str = Header(...)): @app.post("/items/", response_model=Item) -async def create_item(item: Item, x_token: str = Header(...)): +async def create_item(item: Item, x_token: str = Header()): if x_token != fake_secret_token: raise HTTPException(status_code=400, detail="Invalid X-Token header") if item.id in fake_db: diff --git a/docs_src/app_testing/tutorial002.py b/docs_src/app_testing/tutorial002.py index b4a9c0586d..71c898b3cf 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.py b/docs_src/background_tasks/tutorial002.py index e7517e8cde..2e1b2f6c6c 100644 --- a/docs_src/background_tasks/tutorial002.py +++ b/docs_src/background_tasks/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import BackgroundTasks, Depends, FastAPI @@ -10,7 +10,7 @@ def write_log(message: str): log.write(message) -def get_query(background_tasks: BackgroundTasks, q: Optional[str] = None): +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) diff --git a/docs_src/background_tasks/tutorial002_an.py b/docs_src/background_tasks/tutorial002_an.py new file mode 100644 index 0000000000..f63502b09a --- /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 0000000000..1fc78fbc95 --- /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 0000000000..bfdd148753 --- /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/dependencies.py b/docs_src/bigger_applications/app/dependencies.py index 267b0d3a8e..8e45f004b4 100644 --- a/docs_src/bigger_applications/app/dependencies.py +++ b/docs_src/bigger_applications/app/dependencies.py @@ -1,7 +1,7 @@ from fastapi import Header, HTTPException -async def get_token_header(x_token: str = Header(...)): +async def get_token_header(x_token: str = Header()): if x_token != "fake-super-secret-token": raise HTTPException(status_code=400, detail="X-Token header invalid") diff --git a/docs/fr/overrides/.gitignore b/docs_src/bigger_applications/app_an/__init__.py similarity index 100% rename from docs/fr/overrides/.gitignore rename to docs_src/bigger_applications/app_an/__init__.py 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 0000000000..1374c54b31 --- /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/id/overrides/.gitignore b/docs_src/bigger_applications/app_an/internal/__init__.py similarity index 100% rename from docs/id/overrides/.gitignore rename to docs_src/bigger_applications/app_an/internal/__init__.py 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 0000000000..99d3da86b9 --- /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 0000000000..ae544a3aac --- /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/it/overrides/.gitignore b/docs_src/bigger_applications/app_an/routers/__init__.py similarity index 100% rename from docs/it/overrides/.gitignore rename to docs_src/bigger_applications/app_an/routers/__init__.py 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 0000000000..bde9ff4d55 --- /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 0000000000..39b3d7e7cf --- /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/ja/overrides/.gitignore b/docs_src/bigger_applications/app_an_py39/__init__.py similarity index 100% rename from docs/ja/overrides/.gitignore rename to docs_src/bigger_applications/app_an_py39/__init__.py 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 0000000000..5c7612aa09 --- /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/ko/overrides/.gitignore b/docs_src/bigger_applications/app_an_py39/internal/__init__.py similarity index 100% rename from docs/ko/overrides/.gitignore rename to docs_src/bigger_applications/app_an_py39/internal/__init__.py 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 0000000000..99d3da86b9 --- /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 0000000000..ae544a3aac --- /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/nl/overrides/.gitignore b/docs_src/bigger_applications/app_an_py39/routers/__init__.py similarity index 100% rename from docs/nl/overrides/.gitignore rename to docs_src/bigger_applications/app_an_py39/routers/__init__.py 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 0000000000..bde9ff4d55 --- /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 0000000000..39b3d7e7cf --- /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/tutorial001.py b/docs_src/body/tutorial001.py index 52144bd2b0..f933172746 100644 --- a/docs_src/body/tutorial001.py +++ b/docs_src/body/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -6,9 +6,9 @@ from pydantic import BaseModel class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None app = FastAPI() diff --git a/docs_src/body/tutorial002.py b/docs_src/body/tutorial002.py index 644fabae99..7f51839082 100644 --- a/docs_src/body/tutorial002.py +++ b/docs_src/body/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -6,9 +6,9 @@ from pydantic import BaseModel class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None app = FastAPI() diff --git a/docs_src/body/tutorial003.py b/docs_src/body/tutorial003.py index c99ea694b6..89a6b833ce 100644 --- a/docs_src/body/tutorial003.py +++ b/docs_src/body/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -6,9 +6,9 @@ from pydantic import BaseModel class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None app = FastAPI() diff --git a/docs_src/body/tutorial004.py b/docs_src/body/tutorial004.py index 7a222a390e..e2df0df2ba 100644 --- a/docs_src/body/tutorial004.py +++ b/docs_src/body/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -6,16 +6,16 @@ from pydantic import BaseModel class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None app = FastAPI() @app.put("/items/{item_id}") -async def create_item(item_id: int, item: Item, q: Optional[str] = None): +async def create_item(item_id: int, item: Item, q: Union[str, None] = None): result = {"item_id": item_id, **item.dict()} if q: result.update({"q": q}) diff --git a/docs_src/body_fields/tutorial001.py b/docs_src/body_fields/tutorial001.py index dabc48a0f5..cbeebd614a 100644 --- a/docs_src/body_fields/tutorial001.py +++ b/docs_src/body_fields/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel, Field @@ -8,14 +8,14 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = Field( - None, title="The description of the item", max_length=300 + 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: Optional[float] = None + 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: Item = Body(..., embed=True)): +async def update_item(item_id: int, item: Item = Body(embed=True)): results = {"item_id": item_id, "item": item} return results diff --git a/docs_src/body_fields/tutorial001_an.py b/docs_src/body_fields/tutorial001_an.py new file mode 100644 index 0000000000..15ea1b53dc --- /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 0000000000..c9d99e1c2b --- /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 0000000000..6ef14470cd --- /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_fields/tutorial001_py310.py b/docs_src/body_fields/tutorial001_py310.py index 01e02a050d..4437327f3b 100644 --- a/docs_src/body_fields/tutorial001_py310.py +++ b/docs_src/body_fields/tutorial001_py310.py @@ -7,13 +7,13 @@ app = FastAPI() class Item(BaseModel): name: str description: str | None = Field( - None, title="The description of the item", max_length=300 + default=None, title="The description of the item", max_length=300 ) - price: float = Field(..., gt=0, description="The price must be greater than zero") + 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: Item = Body(..., embed=True)): +async def update_item(item_id: int, item: Item = Body(embed=True)): results = {"item_id": item_id, "item": item} return results diff --git a/docs_src/body_multiple_params/tutorial001.py b/docs_src/body_multiple_params/tutorial001.py index 7ce0ae6f28..a73975b3a2 100644 --- a/docs_src/body_multiple_params/tutorial001.py +++ b/docs_src/body_multiple_params/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Path from pydantic import BaseModel @@ -8,17 +8,17 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None @app.put("/items/{item_id}") async def update_item( *, - item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000), - q: Optional[str] = None, - item: Optional[Item] = None, + item_id: 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: 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 0000000000..308eee8544 --- /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 0000000000..2d79c19c28 --- /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 0000000000..1c0ac3a7b3 --- /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/tutorial001_py310.py b/docs_src/body_multiple_params/tutorial001_py310.py index b08d397b3a..be0eba2aee 100644 --- a/docs_src/body_multiple_params/tutorial001_py310.py +++ b/docs_src/body_multiple_params/tutorial001_py310.py @@ -14,7 +14,7 @@ class Item(BaseModel): @app.put("/items/{item_id}") async def update_item( *, - item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000), + item_id: int = Path(title="The ID of the item to get", ge=0, le=1000), q: str | None = None, item: Item | None = None, ): diff --git a/docs_src/body_multiple_params/tutorial002.py b/docs_src/body_multiple_params/tutorial002.py index 6b87484203..2d7160ae8e 100644 --- a/docs_src/body_multiple_params/tutorial002.py +++ b/docs_src/body_multiple_params/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,14 +8,14 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None class User(BaseModel): username: str - full_name: Optional[str] = None + full_name: Union[str, None] = None @app.put("/items/{item_id}") diff --git a/docs_src/body_multiple_params/tutorial003.py b/docs_src/body_multiple_params/tutorial003.py index 7e9e243748..cf344e6c5d 100644 --- a/docs_src/body_multiple_params/tutorial003.py +++ b/docs_src/body_multiple_params/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel @@ -8,19 +8,17 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None class User(BaseModel): username: str - full_name: Optional[str] = None + full_name: Union[str, None] = None @app.put("/items/{item_id}") -async def update_item( - item_id: int, item: Item, user: User, importance: int = Body(...) -): +async def update_item(item_id: int, item: Item, user: User, importance: 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.py b/docs_src/body_multiple_params/tutorial003_an.py new file mode 100644 index 0000000000..39ef7340a5 --- /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 0000000000..593ff893ca --- /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 0000000000..042351e0b7 --- /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/tutorial003_py310.py b/docs_src/body_multiple_params/tutorial003_py310.py index 9ddbda3f7b..a1a75fe8e4 100644 --- a/docs_src/body_multiple_params/tutorial003_py310.py +++ b/docs_src/body_multiple_params/tutorial003_py310.py @@ -17,8 +17,6 @@ class User(BaseModel): @app.put("/items/{item_id}") -async def update_item( - item_id: int, item: Item, user: User, importance: int = Body(...) -): +async def update_item(item_id: int, item: Item, user: User, importance: 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 8dc0d374de..8ce4c7a976 100644 --- a/docs_src/body_multiple_params/tutorial004.py +++ b/docs_src/body_multiple_params/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel @@ -8,14 +8,14 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None class User(BaseModel): username: str - full_name: Optional[str] = None + full_name: Union[str, None] = None @app.put("/items/{item_id}") @@ -24,8 +24,8 @@ async def update_item( item_id: int, item: Item, user: User, - importance: int = Body(..., gt=0), - q: Optional[str] = None + importance: int = Body(gt=0), + 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 0000000000..f6830f3920 --- /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 0000000000..cd5c66fefb --- /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 0000000000..567427c03e --- /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 77321300e6..14acbc3b4b 100644 --- a/docs_src/body_multiple_params/tutorial004_py310.py +++ b/docs_src/body_multiple_params/tutorial004_py310.py @@ -22,8 +22,8 @@ async def update_item( item_id: int, item: Item, user: User, - importance: int = Body(..., gt=0), - q: str | None = None + importance: int = Body(gt=0), + 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.py b/docs_src/body_multiple_params/tutorial005.py index 4657b4144e..29e6e14b7e 100644 --- a/docs_src/body_multiple_params/tutorial005.py +++ b/docs_src/body_multiple_params/tutorial005.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel @@ -8,12 +8,12 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None @app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item = Body(..., embed=True)): +async def update_item(item_id: int, item: Item = Body(embed=True)): results = {"item_id": item_id, "item": item} return results 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 0000000000..dadde80b55 --- /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 0000000000..f97680ba01 --- /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 0000000000..9a52425c16 --- /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/body_multiple_params/tutorial005_py310.py b/docs_src/body_multiple_params/tutorial005_py310.py index 97b213b163..06744507b8 100644 --- a/docs_src/body_multiple_params/tutorial005_py310.py +++ b/docs_src/body_multiple_params/tutorial005_py310.py @@ -12,6 +12,6 @@ class Item(BaseModel): @app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item = Body(..., embed=True)): +async def update_item(item_id: int, item: Item = Body(embed=True)): results = {"item_id": item_id, "item": item} return results diff --git a/docs_src/body_nested_models/tutorial001.py b/docs_src/body_nested_models/tutorial001.py index fe14fdf93e..37ef6dda58 100644 --- a/docs_src/body_nested_models/tutorial001.py +++ b/docs_src/body_nested_models/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: list = [] diff --git a/docs_src/body_nested_models/tutorial002.py b/docs_src/body_nested_models/tutorial002.py index 1770516a45..155cff7885 100644 --- a/docs_src/body_nested_models/tutorial002.py +++ b/docs_src/body_nested_models/tutorial002.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: List[str] = [] diff --git a/docs_src/body_nested_models/tutorial002_py39.py b/docs_src/body_nested_models/tutorial002_py39.py index af523a74e4..8a93a7233f 100644 --- a/docs_src/body_nested_models/tutorial002_py39.py +++ b/docs_src/body_nested_models/tutorial002_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: list[str] = [] diff --git a/docs_src/body_nested_models/tutorial003.py b/docs_src/body_nested_models/tutorial003.py index 33dbbe3a9e..84ed18bf48 100644 --- a/docs_src/body_nested_models/tutorial003.py +++ b/docs_src/body_nested_models/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() diff --git a/docs_src/body_nested_models/tutorial003_py39.py b/docs_src/body_nested_models/tutorial003_py39.py index 931d92f883..b590ece369 100644 --- a/docs_src/body_nested_models/tutorial003_py39.py +++ b/docs_src/body_nested_models/tutorial003_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() diff --git a/docs_src/body_nested_models/tutorial004.py b/docs_src/body_nested_models/tutorial004.py index 311a4e73f4..a07bfacac1 100644 --- a/docs_src/body_nested_models/tutorial004.py +++ b/docs_src/body_nested_models/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel @@ -13,11 +13,11 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() - image: Optional[Image] = None + image: Union[Image, None] = None @app.put("/items/{item_id}") diff --git a/docs_src/body_nested_models/tutorial004_py39.py b/docs_src/body_nested_models/tutorial004_py39.py index ab05da0238..dc2b175fb2 100644 --- a/docs_src/body_nested_models/tutorial004_py39.py +++ b/docs_src/body_nested_models/tutorial004_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -13,11 +13,11 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() - image: Optional[Image] = None + image: Union[Image, None] = None @app.put("/items/{item_id}") diff --git a/docs_src/body_nested_models/tutorial005.py b/docs_src/body_nested_models/tutorial005.py index e76498c3b6..5a01264eda 100644 --- a/docs_src/body_nested_models/tutorial005.py +++ b/docs_src/body_nested_models/tutorial005.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel, HttpUrl @@ -13,11 +13,11 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() - image: Optional[Image] = None + image: Union[Image, None] = None @app.put("/items/{item_id}") diff --git a/docs_src/body_nested_models/tutorial005_py39.py b/docs_src/body_nested_models/tutorial005_py39.py index 5045518837..47db90008a 100644 --- a/docs_src/body_nested_models/tutorial005_py39.py +++ b/docs_src/body_nested_models/tutorial005_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel, HttpUrl @@ -13,11 +13,11 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() - image: Optional[Image] = None + image: Union[Image, None] = None @app.put("/items/{item_id}") diff --git a/docs_src/body_nested_models/tutorial006.py b/docs_src/body_nested_models/tutorial006.py index da78367153..75f1f30e33 100644 --- a/docs_src/body_nested_models/tutorial006.py +++ b/docs_src/body_nested_models/tutorial006.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Set +from typing import List, Set, Union from fastapi import FastAPI from pydantic import BaseModel, HttpUrl @@ -13,11 +13,11 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() - images: Optional[List[Image]] = None + images: Union[List[Image], None] = None @app.put("/items/{item_id}") diff --git a/docs_src/body_nested_models/tutorial006_py39.py b/docs_src/body_nested_models/tutorial006_py39.py index 61898178e2..b14409703a 100644 --- a/docs_src/body_nested_models/tutorial006_py39.py +++ b/docs_src/body_nested_models/tutorial006_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel, HttpUrl @@ -13,11 +13,11 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() - images: Optional[list[Image]] = None + images: Union[list[Image], None] = None @app.put("/items/{item_id}") diff --git a/docs_src/body_nested_models/tutorial007.py b/docs_src/body_nested_models/tutorial007.py index dfbbeaab1b..641f09dced 100644 --- a/docs_src/body_nested_models/tutorial007.py +++ b/docs_src/body_nested_models/tutorial007.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Set +from typing import List, Set, Union from fastapi import FastAPI from pydantic import BaseModel, HttpUrl @@ -13,16 +13,16 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() - images: Optional[List[Image]] = None + images: Union[List[Image], None] = None class Offer(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float items: List[Item] diff --git a/docs_src/body_nested_models/tutorial007_py39.py b/docs_src/body_nested_models/tutorial007_py39.py index 0c7d32fbbe..59cf01e236 100644 --- a/docs_src/body_nested_models/tutorial007_py39.py +++ b/docs_src/body_nested_models/tutorial007_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel, HttpUrl @@ -13,16 +13,16 @@ class Image(BaseModel): class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() - images: Optional[list[Image]] = None + images: Union[list[Image], None] = None class Offer(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float items: list[Item] diff --git a/docs_src/body_updates/tutorial001.py b/docs_src/body_updates/tutorial001.py index 9b8f3ccf1b..4e65d77e26 100644 --- a/docs_src/body_updates/tutorial001.py +++ b/docs_src/body_updates/tutorial001.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI from fastapi.encoders import jsonable_encoder @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - price: Optional[float] = None + name: Union[str, None] = None + description: Union[str, None] = None + price: Union[float, None] = None tax: float = 10.5 tags: List[str] = [] diff --git a/docs_src/body_updates/tutorial001_py39.py b/docs_src/body_updates/tutorial001_py39.py index 5d5388b567..999bcdb82d 100644 --- a/docs_src/body_updates/tutorial001_py39.py +++ b/docs_src/body_updates/tutorial001_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from fastapi.encoders import jsonable_encoder @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - price: Optional[float] = None + name: Union[str, None] = None + description: Union[str, None] = None + price: Union[float, None] = None tax: float = 10.5 tags: list[str] = [] diff --git a/docs_src/body_updates/tutorial002.py b/docs_src/body_updates/tutorial002.py index 46d27e67e7..c3a0fe79ea 100644 --- a/docs_src/body_updates/tutorial002.py +++ b/docs_src/body_updates/tutorial002.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI from fastapi.encoders import jsonable_encoder @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - price: Optional[float] = None + name: Union[str, None] = None + description: Union[str, None] = None + price: Union[float, None] = None tax: float = 10.5 tags: List[str] = [] diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py index ab85bd5ae7..eb35b35215 100644 --- a/docs_src/body_updates/tutorial002_py39.py +++ b/docs_src/body_updates/tutorial002_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from fastapi.encoders import jsonable_encoder @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - price: Optional[float] = None + name: Union[str, None] = None + description: Union[str, None] = None + price: Union[float, None] = None tax: float = 10.5 tags: list[str] = [] diff --git a/docs_src/cookie_params/tutorial001.py b/docs_src/cookie_params/tutorial001.py index 67d03b1336..c4a497fda5 100644 --- a/docs_src/cookie_params/tutorial001.py +++ b/docs_src/cookie_params/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Cookie, FastAPI @@ -6,5 +6,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(ads_id: Optional[str] = Cookie(None)): +async def read_items(ads_id: Union[str, None] = Cookie(default=None)): return {"ads_id": ads_id} diff --git a/docs_src/cookie_params/tutorial001_an.py b/docs_src/cookie_params/tutorial001_an.py new file mode 100644 index 0000000000..6d5931229c --- /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 0000000000..69ac683fed --- /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 0000000000..e18d0a332e --- /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/cookie_params/tutorial001_py310.py b/docs_src/cookie_params/tutorial001_py310.py index d0b0046318..6c9d5f9a1e 100644 --- a/docs_src/cookie_params/tutorial001_py310.py +++ b/docs_src/cookie_params/tutorial001_py310.py @@ -4,5 +4,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(ads_id: str | None = Cookie(None)): +async def read_items(ads_id: str | None = Cookie(default=None)): return {"ads_id": ads_id} diff --git a/docs_src/custom_request_and_route/tutorial001.py b/docs_src/custom_request_and_route/tutorial001.py index 2e64ad45d8..268ce9019e 100644 --- a/docs_src/custom_request_and_route/tutorial001.py +++ b/docs_src/custom_request_and_route/tutorial001.py @@ -31,5 +31,5 @@ app.router.route_class = GzipRoute @app.post("/sum") -async def sum_numbers(numbers: List[int] = Body(...)): +async def sum_numbers(numbers: List[int] = Body()): return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial002.py b/docs_src/custom_request_and_route/tutorial002.py index f4c093ac9c..cee4a95f08 100644 --- a/docs_src/custom_request_and_route/tutorial002.py +++ b/docs_src/custom_request_and_route/tutorial002.py @@ -25,5 +25,5 @@ app.router.route_class = ValidationErrorLoggingRoute @app.post("/") -async def sum_numbers(numbers: List[int] = Body(...)): +async def sum_numbers(numbers: List[int] = Body()): return sum(numbers) diff --git a/docs_src/custom_response/tutorial001b.py b/docs_src/custom_response/tutorial001b.py index e98460372e..95e6ca7637 100644 --- a/docs_src/custom_response/tutorial001b.py +++ b/docs_src/custom_response/tutorial001b.py @@ -6,4 +6,4 @@ app = FastAPI() @app.get("/items/", response_class=ORJSONResponse) async def read_items(): - return [{"item_id": "Foo"}] + return ORJSONResponse([{"item_id": "Foo"}]) diff --git a/docs_src/custom_response/tutorial009c.py b/docs_src/custom_response/tutorial009c.py new file mode 100644 index 0000000000..de6b6688e6 --- /dev/null +++ b/docs_src/custom_response/tutorial009c.py @@ -0,0 +1,19 @@ +from typing import Any + +import orjson +from fastapi import FastAPI, Response + +app = FastAPI() + + +class CustomORJSONResponse(Response): + media_type = "application/json" + + def render(self, content: Any) -> bytes: + assert orjson is not None, "orjson must be installed" + return orjson.dumps(content, option=orjson.OPT_INDENT_2) + + +@app.get("/", response_class=CustomORJSONResponse) +async def main(): + return {"message": "Hello World"} diff --git a/docs_src/dataclasses/tutorial001.py b/docs_src/dataclasses/tutorial001.py index 43015eb27f..2954c391f1 100644 --- a/docs_src/dataclasses/tutorial001.py +++ b/docs_src/dataclasses/tutorial001.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -8,8 +8,8 @@ from fastapi import FastAPI class Item: name: str price: float - description: Optional[str] = None - tax: Optional[float] = None + description: Union[str, None] = None + tax: Union[float, None] = None app = FastAPI() diff --git a/docs_src/dataclasses/tutorial002.py b/docs_src/dataclasses/tutorial002.py index aaa7b8799c..08a2380804 100644 --- a/docs_src/dataclasses/tutorial002.py +++ b/docs_src/dataclasses/tutorial002.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI @@ -9,8 +9,8 @@ class Item: name: str price: float tags: List[str] = field(default_factory=list) - description: Optional[str] = None - tax: Optional[float] = None + description: Union[str, None] = None + tax: Union[float, None] = None app = FastAPI() diff --git a/docs_src/dataclasses/tutorial003.py b/docs_src/dataclasses/tutorial003.py index 2c1fccdd71..34ce1199e5 100644 --- a/docs_src/dataclasses/tutorial003.py +++ b/docs_src/dataclasses/tutorial003.py @@ -1,5 +1,5 @@ from dataclasses import field # (1) -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI from pydantic.dataclasses import dataclass # (2) @@ -8,7 +8,7 @@ from pydantic.dataclasses import dataclass # (2) @dataclass class Item: name: str - description: Optional[str] = None + description: Union[str, None] = None @dataclass diff --git a/docs_src/dependencies/tutorial001.py b/docs_src/dependencies/tutorial001.py index a9da971dc0..b1275103a0 100644 --- a/docs_src/dependencies/tutorial001.py +++ b/docs_src/dependencies/tutorial001.py @@ -1,11 +1,13 @@ -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI app = FastAPI() -async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100): +async def common_parameters( + q: Union[str, None] = None, skip: int = 0, limit: int = 100 +): return {"q": q, "skip": skip, "limit": limit} diff --git a/docs_src/dependencies/tutorial001_02_an.py b/docs_src/dependencies/tutorial001_02_an.py new file mode 100644 index 0000000000..455d60c822 --- /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 0000000000..f59637bb23 --- /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 0000000000..df969ae9c8 --- /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 0000000000..81e24fe86c --- /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 0000000000..f2e57ae7b8 --- /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 0000000000..5d9fe6ddfd --- /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.py b/docs_src/dependencies/tutorial002.py index 458f6b5bb2..8e863e4fab 100644 --- a/docs_src/dependencies/tutorial002.py +++ b/docs_src/dependencies/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI @@ -9,7 +9,7 @@ fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz" class CommonQueryParams: - def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100): + def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): self.q = q self.skip = skip self.limit = limit diff --git a/docs_src/dependencies/tutorial002_an.py b/docs_src/dependencies/tutorial002_an.py new file mode 100644 index 0000000000..964ccf66ca --- /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 0000000000..0777263125 --- /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 0000000000..844a23c5a7 --- /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.py b/docs_src/dependencies/tutorial003.py index 3f3e940f81..34614e5397 100644 --- a/docs_src/dependencies/tutorial003.py +++ b/docs_src/dependencies/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI @@ -9,7 +9,7 @@ fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz" class CommonQueryParams: - def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100): + def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): self.q = q self.skip = skip self.limit = limit diff --git a/docs_src/dependencies/tutorial003_an.py b/docs_src/dependencies/tutorial003_an.py new file mode 100644 index 0000000000..ba8e9f7174 --- /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 0000000000..44116989b7 --- /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 0000000000..9e9123ad2f --- /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.py b/docs_src/dependencies/tutorial004.py index daa7b46703..d9fe881488 100644 --- a/docs_src/dependencies/tutorial004.py +++ b/docs_src/dependencies/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI @@ -9,7 +9,7 @@ fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz" class CommonQueryParams: - def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100): + def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): self.q = q self.skip = skip self.limit = limit diff --git a/docs_src/dependencies/tutorial004_an.py b/docs_src/dependencies/tutorial004_an.py new file mode 100644 index 0000000000..78881a354c --- /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 0000000000..2c009efccd --- /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 0000000000..74268626b4 --- /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.py b/docs_src/dependencies/tutorial005.py index c8923d143d..697332b5ba 100644 --- a/docs_src/dependencies/tutorial005.py +++ b/docs_src/dependencies/tutorial005.py @@ -1,16 +1,17 @@ -from typing import Optional +from typing import Union from fastapi import Cookie, Depends, FastAPI app = FastAPI() -def query_extractor(q: Optional[str] = None): +def query_extractor(q: Union[str, None] = None): return q def query_or_cookie_extractor( - q: str = Depends(query_extractor), last_query: Optional[str] = Cookie(None) + q: str = Depends(query_extractor), + last_query: Union[str, None] = Cookie(default=None), ): if not q: return last_query diff --git a/docs_src/dependencies/tutorial005_an.py b/docs_src/dependencies/tutorial005_an.py new file mode 100644 index 0000000000..6785099dab --- /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 0000000000..6c0aa0b368 --- /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 0000000000..e8887e1621 --- /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/tutorial005_py310.py b/docs_src/dependencies/tutorial005_py310.py index 5e1d7e0ef0..247cdabe21 100644 --- a/docs_src/dependencies/tutorial005_py310.py +++ b/docs_src/dependencies/tutorial005_py310.py @@ -8,7 +8,7 @@ def query_extractor(q: str | None = None): def query_or_cookie_extractor( - q: str = Depends(query_extractor), last_query: str | None = Cookie(None) + q: str = Depends(query_extractor), last_query: str | None = Cookie(default=None) ): if not q: return last_query diff --git a/docs_src/dependencies/tutorial006.py b/docs_src/dependencies/tutorial006.py index a71d7cce6e..9aff4154f4 100644 --- a/docs_src/dependencies/tutorial006.py +++ b/docs_src/dependencies/tutorial006.py @@ -3,12 +3,12 @@ from fastapi import Depends, FastAPI, Header, HTTPException app = FastAPI() -async def verify_token(x_token: str = Header(...)): +async def verify_token(x_token: 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: str = Header(...)): +async def verify_key(x_key: str = Header()): if x_key != "fake-super-secret-key": raise HTTPException(status_code=400, detail="X-Key header invalid") return x_key diff --git a/docs_src/dependencies/tutorial006_an.py b/docs_src/dependencies/tutorial006_an.py new file mode 100644 index 0000000000..5aaea04d16 --- /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 0000000000..11976ed6a2 --- /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 0000000000..2de86f042d --- /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 0000000000..acc804c320 --- /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 0000000000..6c13d9033f --- /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 0000000000..68b7434ec2 --- /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.py b/docs_src/dependencies/tutorial012.py index 8f8868a559..36ce6c7111 100644 --- a/docs_src/dependencies/tutorial012.py +++ b/docs_src/dependencies/tutorial012.py @@ -1,12 +1,12 @@ from fastapi import Depends, FastAPI, Header, HTTPException -async def verify_token(x_token: str = Header(...)): +async def verify_token(x_token: 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: str = Header(...)): +async def verify_key(x_key: str = Header()): if x_key != "fake-super-secret-key": raise HTTPException(status_code=400, detail="X-Key header invalid") return x_key diff --git a/docs_src/dependencies/tutorial012_an.py b/docs_src/dependencies/tutorial012_an.py new file mode 100644 index 0000000000..7541e6bf41 --- /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 0000000000..7541e6bf41 --- /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.py b/docs_src/dependency_testing/tutorial001.py index 237d3b2310..a5fe1d9bff 100644 --- a/docs_src/dependency_testing/tutorial001.py +++ b/docs_src/dependency_testing/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI from fastapi.testclient import TestClient @@ -6,7 +6,9 @@ from fastapi.testclient import TestClient app = FastAPI() -async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100): +async def common_parameters( + q: Union[str, None] = None, skip: int = 0, limit: int = 100 +): return {"q": q, "skip": skip, "limit": limit} @@ -23,7 +25,7 @@ async def read_users(commons: dict = Depends(common_parameters)): client = TestClient(app) -async def override_dependency(q: Optional[str] = None): +async def override_dependency(q: Union[str, None] = None): return {"q": q, "skip": 5, "limit": 10} diff --git a/docs_src/dependency_testing/tutorial001_an.py b/docs_src/dependency_testing/tutorial001_an.py new file mode 100644 index 0000000000..4c76a87ff5 --- /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 0000000000..f866e7a1bb --- /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 0000000000..bccb0cdb1f --- /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 0000000000..d1f6d4374f --- /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/encoder/tutorial001.py b/docs_src/encoder/tutorial001.py index a918fdd64a..5f7e7061e4 100644 --- a/docs_src/encoder/tutorial001.py +++ b/docs_src/encoder/tutorial001.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Optional +from typing import Union from fastapi import FastAPI from fastapi.encoders import jsonable_encoder @@ -11,7 +11,7 @@ fake_db = {} class Item(BaseModel): title: str timestamp: datetime - description: Optional[str] = None + description: Union[str, None] = None app = FastAPI() diff --git a/docs_src/events/tutorial003.py b/docs_src/events/tutorial003.py new file mode 100644 index 0000000000..2b650590b0 --- /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.py b/docs_src/extra_data_types/tutorial001.py index e8d7e1ea32..8ae8472a70 100644 --- a/docs_src/extra_data_types/tutorial001.py +++ b/docs_src/extra_data_types/tutorial001.py @@ -1,5 +1,5 @@ from datetime import datetime, time, timedelta -from typing import Optional +from typing import Union from uuid import UUID from fastapi import Body, FastAPI @@ -10,10 +10,10 @@ app = FastAPI() @app.put("/items/{item_id}") async def read_items( item_id: UUID, - start_datetime: Optional[datetime] = Body(None), - end_datetime: Optional[datetime] = Body(None), - repeat_at: Optional[time] = Body(None), - process_after: Optional[timedelta] = Body(None), + start_datetime: Union[datetime, None] = Body(default=None), + end_datetime: Union[datetime, None] = Body(default=None), + repeat_at: Union[time, None] = Body(default=None), + process_after: Union[timedelta, None] = Body(default=None), ): start_process = start_datetime + process_after duration = end_datetime - start_process 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 0000000000..a4c074241a --- /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 0000000000..4f69c40d95 --- /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 0000000000..630d36ae31 --- /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/extra_data_types/tutorial001_py310.py b/docs_src/extra_data_types/tutorial001_py310.py index 4a33481b77..d22f818886 100644 --- a/docs_src/extra_data_types/tutorial001_py310.py +++ b/docs_src/extra_data_types/tutorial001_py310.py @@ -9,10 +9,10 @@ app = FastAPI() @app.put("/items/{item_id}") async def read_items( item_id: UUID, - start_datetime: datetime | None = Body(None), - end_datetime: datetime | None = Body(None), - repeat_at: time | None = Body(None), - process_after: timedelta | None = Body(None), + start_datetime: datetime | None = Body(default=None), + end_datetime: datetime | None = Body(default=None), + repeat_at: time | None = Body(default=None), + process_after: timedelta | None = Body(default=None), ): start_process = start_datetime + process_after duration = end_datetime - start_process diff --git a/docs_src/extra_models/tutorial001.py b/docs_src/extra_models/tutorial001.py index e95844f608..4be56cd2a7 100644 --- a/docs_src/extra_models/tutorial001.py +++ b/docs_src/extra_models/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel, EmailStr @@ -10,20 +10,20 @@ class UserIn(BaseModel): username: str password: str email: EmailStr - full_name: Optional[str] = None + full_name: Union[str, None] = None class UserOut(BaseModel): username: str email: EmailStr - full_name: Optional[str] = None + full_name: Union[str, None] = None class UserInDB(BaseModel): username: str hashed_password: str email: EmailStr - full_name: Optional[str] = None + full_name: Union[str, None] = None def fake_password_hasher(raw_password: str): diff --git a/docs_src/extra_models/tutorial002.py b/docs_src/extra_models/tutorial002.py index 5bc6e707fa..70fa16441d 100644 --- a/docs_src/extra_models/tutorial002.py +++ b/docs_src/extra_models/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel, EmailStr @@ -9,7 +9,7 @@ app = FastAPI() class UserBase(BaseModel): username: str email: EmailStr - full_name: Optional[str] = None + full_name: Union[str, None] = None class UserIn(UserBase): diff --git a/docs_src/header_params/tutorial001.py b/docs_src/header_params/tutorial001.py index 7d69b027ee..74429c8e2d 100644 --- a/docs_src/header_params/tutorial001.py +++ b/docs_src/header_params/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Header @@ -6,5 +6,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(user_agent: Optional[str] = Header(None)): +async def read_items(user_agent: Union[str, None] = Header(default=None)): return {"User-Agent": user_agent} diff --git a/docs_src/header_params/tutorial001_an.py b/docs_src/header_params/tutorial001_an.py new file mode 100644 index 0000000000..816c000862 --- /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 0000000000..4ba5e1bfbe --- /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 0000000000..1fbe3bb99a --- /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/tutorial001_py310.py b/docs_src/header_params/tutorial001_py310.py index b284633465..2203ed1b8b 100644 --- a/docs_src/header_params/tutorial001_py310.py +++ b/docs_src/header_params/tutorial001_py310.py @@ -4,5 +4,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(user_agent: str | None = Header(None)): +async def read_items(user_agent: str | None = Header(default=None)): return {"User-Agent": user_agent} diff --git a/docs_src/header_params/tutorial002.py b/docs_src/header_params/tutorial002.py index 2de3dddd70..639ab1735d 100644 --- a/docs_src/header_params/tutorial002.py +++ b/docs_src/header_params/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Header @@ -7,6 +7,6 @@ app = FastAPI() @app.get("/items/") async def read_items( - strange_header: Optional[str] = Header(None, convert_underscores=False) + strange_header: Union[str, None] = Header(default=None, convert_underscores=False) ): return {"strange_header": strange_header} diff --git a/docs_src/header_params/tutorial002_an.py b/docs_src/header_params/tutorial002_an.py new file mode 100644 index 0000000000..65d972d46e --- /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 0000000000..b340647b60 --- /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 0000000000..7f6a99f9c3 --- /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/tutorial002_py310.py b/docs_src/header_params/tutorial002_py310.py index 98ab5a807d..b7979b542a 100644 --- a/docs_src/header_params/tutorial002_py310.py +++ b/docs_src/header_params/tutorial002_py310.py @@ -5,6 +5,6 @@ app = FastAPI() @app.get("/items/") async def read_items( - strange_header: str | None = Header(None, convert_underscores=False) + strange_header: str | None = Header(default=None, convert_underscores=False) ): return {"strange_header": strange_header} diff --git a/docs_src/header_params/tutorial003.py b/docs_src/header_params/tutorial003.py index 6d0eefdd21..a61314aedb 100644 --- a/docs_src/header_params/tutorial003.py +++ b/docs_src/header_params/tutorial003.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI, Header @@ -6,5 +6,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(x_token: Optional[List[str]] = Header(None)): +async def read_items(x_token: Union[List[str], None] = Header(default=None)): return {"X-Token values": x_token} diff --git a/docs_src/header_params/tutorial003_an.py b/docs_src/header_params/tutorial003_an.py new file mode 100644 index 0000000000..5406fd1f88 --- /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 0000000000..0e78cf0290 --- /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 0000000000..c1dd499611 --- /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/header_params/tutorial003_py310.py b/docs_src/header_params/tutorial003_py310.py index 2dac2c13cf..435c67574b 100644 --- a/docs_src/header_params/tutorial003_py310.py +++ b/docs_src/header_params/tutorial003_py310.py @@ -4,5 +4,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(x_token: list[str] | None = Header(None)): +async def read_items(x_token: list[str] | None = Header(default=None)): return {"X-Token values": x_token} diff --git a/docs_src/header_params/tutorial003_py39.py b/docs_src/header_params/tutorial003_py39.py index 359766527e..34437db161 100644 --- a/docs_src/header_params/tutorial003_py39.py +++ b/docs_src/header_params/tutorial003_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Header @@ -6,5 +6,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(x_token: Optional[list[str]] = Header(None)): +async def read_items(x_token: Union[list[str], None] = Header(default=None)): return {"X-Token values": x_token} diff --git a/docs_src/nosql_databases/tutorial001.py b/docs_src/nosql_databases/tutorial001.py index 39548d8625..91893e5281 100644 --- a/docs_src/nosql_databases/tutorial001.py +++ b/docs_src/nosql_databases/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from couchbase import LOCKMODE_WAIT from couchbase.bucket import Bucket @@ -23,9 +23,9 @@ def get_bucket(): class User(BaseModel): username: str - email: Optional[str] = None - full_name: Optional[str] = None - disabled: Optional[bool] = None + email: Union[str, None] = None + full_name: Union[str, None] = None + disabled: Union[bool, None] = None class UserInDB(User): diff --git a/docs_src/openapi_callbacks/tutorial001.py b/docs_src/openapi_callbacks/tutorial001.py index 2fb8367515..3f1bac6e29 100644 --- a/docs_src/openapi_callbacks/tutorial001.py +++ b/docs_src/openapi_callbacks/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import APIRouter, FastAPI from pydantic import BaseModel, HttpUrl @@ -8,7 +8,7 @@ app = FastAPI() class Invoice(BaseModel): id: str - title: Optional[str] = None + title: Union[str, None] = None customer: str total: float @@ -33,7 +33,7 @@ def invoice_notification(body: InvoiceEvent): @app.post("/invoices/", callbacks=invoices_callback_router.routes) -def create_invoice(invoice: Invoice, callback_url: Optional[HttpUrl] = None): +def create_invoice(invoice: Invoice, callback_url: Union[HttpUrl, None] = None): """ Create an invoice. diff --git a/docs_src/path_operation_advanced_configuration/tutorial004.py b/docs_src/path_operation_advanced_configuration/tutorial004.py index da678aed3a..a3aad4ac40 100644 --- a/docs_src/path_operation_advanced_configuration/tutorial004.py +++ b/docs_src/path_operation_advanced_configuration/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial001.py b/docs_src/path_operation_configuration/tutorial001.py index 1316d9237e..83fd8377ab 100644 --- a/docs_src/path_operation_configuration/tutorial001.py +++ b/docs_src/path_operation_configuration/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI, status from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial001_py39.py b/docs_src/path_operation_configuration/tutorial001_py39.py index 5c04d8bac0..a9dcbf3898 100644 --- a/docs_src/path_operation_configuration/tutorial001_py39.py +++ b/docs_src/path_operation_configuration/tutorial001_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, status from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial002.py b/docs_src/path_operation_configuration/tutorial002.py index 2df537d864..798b0c2311 100644 --- a/docs_src/path_operation_configuration/tutorial002.py +++ b/docs_src/path_operation_configuration/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial002_py39.py b/docs_src/path_operation_configuration/tutorial002_py39.py index 766d9fb0b7..e7ced7de7e 100644 --- a/docs_src/path_operation_configuration/tutorial002_py39.py +++ b/docs_src/path_operation_configuration/tutorial002_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial003.py b/docs_src/path_operation_configuration/tutorial003.py index 269a1a253d..26bf7dabae 100644 --- a/docs_src/path_operation_configuration/tutorial003.py +++ b/docs_src/path_operation_configuration/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial003_py39.py b/docs_src/path_operation_configuration/tutorial003_py39.py index 446198b5c8..607c5707e6 100644 --- a/docs_src/path_operation_configuration/tutorial003_py39.py +++ b/docs_src/path_operation_configuration/tutorial003_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial004.py b/docs_src/path_operation_configuration/tutorial004.py index de83be836b..8f865c58a6 100644 --- a/docs_src/path_operation_configuration/tutorial004.py +++ b/docs_src/path_operation_configuration/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial004_py39.py b/docs_src/path_operation_configuration/tutorial004_py39.py index bf6005b950..fc25680c5a 100644 --- a/docs_src/path_operation_configuration/tutorial004_py39.py +++ b/docs_src/path_operation_configuration/tutorial004_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial005.py b/docs_src/path_operation_configuration/tutorial005.py index 0f62c38146..2c1be4a34b 100644 --- a/docs_src/path_operation_configuration/tutorial005.py +++ b/docs_src/path_operation_configuration/tutorial005.py @@ -1,4 +1,4 @@ -from typing import Optional, Set +from typing import Set, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: Set[str] = set() diff --git a/docs_src/path_operation_configuration/tutorial005_py39.py b/docs_src/path_operation_configuration/tutorial005_py39.py index 5ef3204055..ddf29b733d 100644 --- a/docs_src/path_operation_configuration/tutorial005_py39.py +++ b/docs_src/path_operation_configuration/tutorial005_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: set[str] = set() diff --git a/docs_src/path_params/tutorial005.py b/docs_src/path_params/tutorial005.py index 8e37677449..9a24a4963a 100644 --- a/docs_src/path_params/tutorial005.py +++ b/docs_src/path_params/tutorial005.py @@ -14,7 +14,7 @@ app = FastAPI() @app.get("/models/{model_name}") async def get_model(model_name: ModelName): - if model_name == ModelName.alexnet: + if model_name is ModelName.alexnet: return {"model_name": model_name, "message": "Deep Learning FTW!"} if model_name.value == "lenet": diff --git a/docs_src/path_params_numeric_validations/tutorial001.py b/docs_src/path_params_numeric_validations/tutorial001.py index 11777bba77..5301470282 100644 --- a/docs_src/path_params_numeric_validations/tutorial001.py +++ b/docs_src/path_params_numeric_validations/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Path, Query @@ -7,8 +7,8 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_items( - item_id: int = Path(..., title="The ID of the item to get"), - q: Optional[str] = Query(None, alias="item-query"), + item_id: int = Path(title="The ID of the item to get"), + q: Union[str, None] = Query(default=None, alias="item-query"), ): results = {"item_id": item_id} if q: 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 0000000000..621be7b045 --- /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 0000000000..c4db263f0e --- /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 0000000000..b36315a46d --- /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/tutorial001_py310.py b/docs_src/path_params_numeric_validations/tutorial001_py310.py index b940a0949f..b1a77cc9dd 100644 --- a/docs_src/path_params_numeric_validations/tutorial001_py310.py +++ b/docs_src/path_params_numeric_validations/tutorial001_py310.py @@ -5,8 +5,8 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_items( - item_id: int = Path(..., title="The ID of the item to get"), - q: str | None = Query(None, alias="item-query"), + item_id: int = Path(title="The ID of the item to get"), + q: str | None = Query(default=None, alias="item-query"), ): results = {"item_id": item_id} if q: diff --git a/docs_src/path_params_numeric_validations/tutorial002.py b/docs_src/path_params_numeric_validations/tutorial002.py index 57ca50ece0..63ac691a83 100644 --- a/docs_src/path_params_numeric_validations/tutorial002.py +++ b/docs_src/path_params_numeric_validations/tutorial002.py @@ -4,9 +4,7 @@ app = FastAPI() @app.get("/items/{item_id}") -async def read_items( - q: str, item_id: int = Path(..., title="The ID of the item to get") -): +async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")): results = {"item_id": item_id} if q: results.update({"q": q}) 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 0000000000..322f8cf0bb --- /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 0000000000..cd882abb2c --- /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.py b/docs_src/path_params_numeric_validations/tutorial003.py index b6b5a19869..8df0ffc620 100644 --- a/docs_src/path_params_numeric_validations/tutorial003.py +++ b/docs_src/path_params_numeric_validations/tutorial003.py @@ -4,9 +4,7 @@ app = FastAPI() @app.get("/items/{item_id}") -async def read_items( - *, item_id: int = Path(..., title="The ID of the item to get"), q: str -): +async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): results = {"item_id": item_id} if q: results.update({"q": q}) 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 0000000000..d0fa8b3db9 --- /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 0000000000..1588556e78 --- /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.py b/docs_src/path_params_numeric_validations/tutorial004.py index 2ec7082801..86651d47cf 100644 --- a/docs_src/path_params_numeric_validations/tutorial004.py +++ b/docs_src/path_params_numeric_validations/tutorial004.py @@ -5,7 +5,7 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_items( - *, item_id: int = Path(..., title="The ID of the item to get", ge=1), q: str + *, item_id: int = Path(title="The ID of the item to get", ge=1), q: str ): results = {"item_id": item_id} if q: 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 0000000000..ffc50f6c5b --- /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 0000000000..f67f6450e7 --- /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.py b/docs_src/path_params_numeric_validations/tutorial005.py index 2809f37b27..8f12f2da02 100644 --- a/docs_src/path_params_numeric_validations/tutorial005.py +++ b/docs_src/path_params_numeric_validations/tutorial005.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_items( *, - item_id: int = Path(..., title="The ID of the item to get", gt=0, le=1000), + item_id: int = Path(title="The ID of the item to get", gt=0, le=1000), q: str, ): results = {"item_id": item_id} 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 0000000000..433c69129f --- /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 0000000000..571dd583ce --- /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 0c19579f5e..0ea32694ae 100644 --- a/docs_src/path_params_numeric_validations/tutorial006.py +++ b/docs_src/path_params_numeric_validations/tutorial006.py @@ -6,9 +6,9 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_items( *, - item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000), + 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 0000000000..22a1436236 --- /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 0000000000..804751893c --- /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/tutorial009c.py b/docs_src/python_types/tutorial009c.py new file mode 100644 index 0000000000..2f539a34b1 --- /dev/null +++ b/docs_src/python_types/tutorial009c.py @@ -0,0 +1,5 @@ +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") diff --git a/docs_src/python_types/tutorial009c_py310.py b/docs_src/python_types/tutorial009c_py310.py new file mode 100644 index 0000000000..96b1220fcc --- /dev/null +++ b/docs_src/python_types/tutorial009c_py310.py @@ -0,0 +1,2 @@ +def say_hi(name: str | None): + print(f"Hey {name}!") diff --git a/docs_src/python_types/tutorial011.py b/docs_src/python_types/tutorial011.py index 047b633b57..c8634cbff5 100644 --- a/docs_src/python_types/tutorial011.py +++ b/docs_src/python_types/tutorial011.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List, Optional +from typing import List, Union from pydantic import BaseModel @@ -7,7 +7,7 @@ from pydantic import BaseModel class User(BaseModel): id: int name = "John Doe" - signup_ts: Optional[datetime] = None + signup_ts: Union[datetime, None] = None friends: List[int] = [] diff --git a/docs_src/python_types/tutorial011_py39.py b/docs_src/python_types/tutorial011_py39.py index af79e2df03..468496f519 100644 --- a/docs_src/python_types/tutorial011_py39.py +++ b/docs_src/python_types/tutorial011_py39.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Optional +from typing import Union from pydantic import BaseModel @@ -7,7 +7,7 @@ from pydantic import BaseModel class User(BaseModel): id: int name = "John Doe" - signup_ts: Optional[datetime] = None + signup_ts: Union[datetime, None] = None friends: list[int] = [] diff --git a/docs_src/python_types/tutorial012.py b/docs_src/python_types/tutorial012.py new file mode 100644 index 0000000000..74fa94c432 --- /dev/null +++ b/docs_src/python_types/tutorial012.py @@ -0,0 +1,8 @@ +from typing import Optional + +from pydantic import BaseModel + + +class User(BaseModel): + name: str + age: Optional[int] diff --git a/docs_src/python_types/tutorial013.py b/docs_src/python_types/tutorial013.py new file mode 100644 index 0000000000..0ec7735199 --- /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 0000000000..65a0eaa939 --- /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/tutorial002.py b/docs_src/query_params/tutorial002.py index 32918465e5..8465f45eed 100644 --- a/docs_src/query_params/tutorial002.py +++ b/docs_src/query_params/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/{item_id}") -async def read_item(item_id: str, q: Optional[str] = None): +async def read_item(item_id: str, q: Union[str, None] = None): if q: return {"item_id": item_id, "q": q} return {"item_id": item_id} diff --git a/docs_src/query_params/tutorial003.py b/docs_src/query_params/tutorial003.py index c81a967856..3362715b39 100644 --- a/docs_src/query_params/tutorial003.py +++ b/docs_src/query_params/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/{item_id}") -async def read_item(item_id: str, q: Optional[str] = None, short: bool = False): +async def read_item(item_id: str, q: Union[str, None] = None, short: bool = False): item = {"item_id": item_id} if q: item.update({"q": q}) diff --git a/docs_src/query_params/tutorial004.py b/docs_src/query_params/tutorial004.py index 37f97fa2ac..049c3ae934 100644 --- a/docs_src/query_params/tutorial004.py +++ b/docs_src/query_params/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -7,7 +7,7 @@ app = FastAPI() @app.get("/users/{user_id}/items/{item_id}") async def read_user_item( - user_id: int, item_id: str, q: Optional[str] = None, short: bool = False + user_id: int, item_id: str, q: Union[str, None] = None, short: bool = False ): item = {"item_id": item_id, "owner_id": user_id} if q: diff --git a/docs_src/query_params/tutorial006.py b/docs_src/query_params/tutorial006.py index ffe3283403..f0dbfe08fe 100644 --- a/docs_src/query_params/tutorial006.py +++ b/docs_src/query_params/tutorial006.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -7,7 +7,7 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_user_item( - item_id: str, needy: str, skip: int = 0, limit: Optional[int] = None + item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None ): item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} return item diff --git a/docs_src/query_params_str_validations/tutorial001.py b/docs_src/query_params_str_validations/tutorial001.py index 5d7bfb0ee5..e38326b18c 100644 --- a/docs_src/query_params_str_validations/tutorial001.py +++ b/docs_src/query_params_str_validations/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Optional[str] = None): +async def read_items(q: Union[str, None] = None): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial002.py b/docs_src/query_params_str_validations/tutorial002.py index 68ea582061..17e017b7e7 100644 --- a/docs_src/query_params_str_validations/tutorial002.py +++ b/docs_src/query_params_str_validations/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Optional[str] = Query(None, max_length=50)): +async def read_items(q: Union[str, None] = Query(default=None, max_length=50)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) 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 0000000000..cb1b38940c --- /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 0000000000..66ee7451b0 --- /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/tutorial002_py310.py b/docs_src/query_params_str_validations/tutorial002_py310.py index fa3139d5aa..f15351d290 100644 --- a/docs_src/query_params_str_validations/tutorial002_py310.py +++ b/docs_src/query_params_str_validations/tutorial002_py310.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str | None = Query(None, max_length=50)): +async def read_items(q: str | None = Query(default=None, max_length=50)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial003.py b/docs_src/query_params_str_validations/tutorial003.py index e52acc72f0..73d2e08c8e 100644 --- a/docs_src/query_params_str_validations/tutorial003.py +++ b/docs_src/query_params_str_validations/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -6,7 +6,9 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Optional[str] = Query(None, min_length=3, max_length=50)): +async def read_items( + q: Union[str, None] = Query(default=None, min_length=3, max_length=50) +): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) 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 0000000000..a3665f6a85 --- /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 0000000000..836af04de0 --- /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 0000000000..87a4268394 --- /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/tutorial003_py310.py b/docs_src/query_params_str_validations/tutorial003_py310.py index 335858a404..dc60ecb39d 100644 --- a/docs_src/query_params_str_validations/tutorial003_py310.py +++ b/docs_src/query_params_str_validations/tutorial003_py310.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str | None = Query(None, min_length=3, max_length=50)): +async def read_items(q: str | None = Query(default=None, min_length=3, max_length=50)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial004.py b/docs_src/query_params_str_validations/tutorial004.py index d2c30331f6..5a7129816c 100644 --- a/docs_src/query_params_str_validations/tutorial004.py +++ b/docs_src/query_params_str_validations/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -7,7 +7,9 @@ app = FastAPI() @app.get("/items/") async def read_items( - q: Optional[str] = Query(None, min_length=3, max_length=50, regex="^fixedquery$") + q: Union[str, None] = Query( + default=None, min_length=3, max_length=50, regex="^fixedquery$" + ) ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: 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 0000000000..5346b997bd --- /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 0000000000..8fd375b3d5 --- /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 0000000000..2fd82db754 --- /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/tutorial004_py310.py b/docs_src/query_params_str_validations/tutorial004_py310.py index 518b779f70..180a2e5112 100644 --- a/docs_src/query_params_str_validations/tutorial004_py310.py +++ b/docs_src/query_params_str_validations/tutorial004_py310.py @@ -5,7 +5,8 @@ app = FastAPI() @app.get("/items/") async def read_items( - q: str | None = Query(None, min_length=3, max_length=50, regex="^fixedquery$") + q: str + | None = Query(default=None, min_length=3, max_length=50, regex="^fixedquery$") ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: diff --git a/docs_src/query_params_str_validations/tutorial005.py b/docs_src/query_params_str_validations/tutorial005.py index 22eb3acba1..8ab42869e6 100644 --- a/docs_src/query_params_str_validations/tutorial005.py +++ b/docs_src/query_params_str_validations/tutorial005.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str = Query("fixedquery", min_length=3)): +async def read_items(q: str = Query(default="fixedquery", min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial005_an.py b/docs_src/query_params_str_validations/tutorial005_an.py new file mode 100644 index 0000000000..452d4d38d7 --- /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 0000000000..b1f6046b50 --- /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.py b/docs_src/query_params_str_validations/tutorial006.py index 720bf07f1a..9a90eb64ef 100644 --- a/docs_src/query_params_str_validations/tutorial006.py +++ b/docs_src/query_params_str_validations/tutorial006.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str = Query(..., min_length=3)): +async def read_items(q: str = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006_an.py b/docs_src/query_params_str_validations/tutorial006_an.py new file mode 100644 index 0000000000..559480d2bf --- /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 0000000000..3b4a676d2e --- /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.py b/docs_src/query_params_str_validations/tutorial006b.py new file mode 100644 index 0000000000..a8d69c8899 --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial006b.py @@ -0,0 +1,11 @@ +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: str = Query(default=..., min_length=3)): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/query_params_str_validations/tutorial006b_an.py b/docs_src/query_params_str_validations/tutorial006b_an.py new file mode 100644 index 0000000000..ea3b02583a --- /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 0000000000..687a9f5446 --- /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.py b/docs_src/query_params_str_validations/tutorial006c.py new file mode 100644 index 0000000000..2ac148c94f --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial006c.py @@ -0,0 +1,13 @@ +from typing import Union + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: Union[str, None] = Query(default=..., min_length=3)): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py new file mode 100644 index 0000000000..10bf26a577 --- /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 0000000000..1ab0a7d53d --- /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 0000000000..ac12733318 --- /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/tutorial006c_py310.py b/docs_src/query_params_str_validations/tutorial006c_py310.py new file mode 100644 index 0000000000..82dd9e5d7c --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial006c_py310.py @@ -0,0 +1,11 @@ +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: str | None = Query(default=..., min_length=3)): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/query_params_str_validations/tutorial006d.py b/docs_src/query_params_str_validations/tutorial006d.py new file mode 100644 index 0000000000..42c5bf4ebb --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial006d.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI, Query +from pydantic import Required + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: str = Query(default=Required, 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 0000000000..bc8283e153 --- /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 0000000000..035d9e3bdd --- /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.py b/docs_src/query_params_str_validations/tutorial007.py index e360feda9f..cb836569e9 100644 --- a/docs_src/query_params_str_validations/tutorial007.py +++ b/docs_src/query_params_str_validations/tutorial007.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -7,7 +7,7 @@ app = FastAPI() @app.get("/items/") async def read_items( - q: Optional[str] = Query(None, title="Query string", min_length=3) + q: Union[str, None] = Query(default=None, title="Query string", min_length=3) ): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: 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 0000000000..3bc85cc0cf --- /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 0000000000..5933911fdf --- /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 0000000000..dafa1c5c94 --- /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/tutorial007_py310.py b/docs_src/query_params_str_validations/tutorial007_py310.py index 14ef4cb69f..e3e1ef2e08 100644 --- a/docs_src/query_params_str_validations/tutorial007_py310.py +++ b/docs_src/query_params_str_validations/tutorial007_py310.py @@ -4,7 +4,9 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str | None = Query(None, title="Query string", min_length=3)): +async def read_items( + q: str | None = Query(default=None, title="Query string", min_length=3) +): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial008.py b/docs_src/query_params_str_validations/tutorial008.py index 238add4710..d112a9ab8a 100644 --- a/docs_src/query_params_str_validations/tutorial008.py +++ b/docs_src/query_params_str_validations/tutorial008.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -7,8 +7,8 @@ app = FastAPI() @app.get("/items/") async def read_items( - q: Optional[str] = Query( - None, + q: Union[str, None] = Query( + default=None, title="Query string", description="Query string for the items to search in the database that have a good match", min_length=3, 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 0000000000..5699f1e88d --- /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 0000000000..4aaadf8b47 --- /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 0000000000..1c3b361765 --- /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/tutorial008_py310.py b/docs_src/query_params_str_validations/tutorial008_py310.py index 06bb02442b..489f631d5e 100644 --- a/docs_src/query_params_str_validations/tutorial008_py310.py +++ b/docs_src/query_params_str_validations/tutorial008_py310.py @@ -7,7 +7,7 @@ app = FastAPI() async def read_items( q: str | None = Query( - None, + default=None, title="Query string", description="Query string for the items to search in the database that have a good match", min_length=3, diff --git a/docs_src/query_params_str_validations/tutorial009.py b/docs_src/query_params_str_validations/tutorial009.py index 7e5c0b81a8..8a6bfe2d93 100644 --- a/docs_src/query_params_str_validations/tutorial009.py +++ b/docs_src/query_params_str_validations/tutorial009.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Optional[str] = Query(None, alias="item-query")): +async def read_items(q: Union[str, None] = Query(default=None, alias="item-query")): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) 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 0000000000..2894e2d51a --- /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 0000000000..d11753362b --- /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 0000000000..70a89e6137 --- /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/tutorial009_py310.py b/docs_src/query_params_str_validations/tutorial009_py310.py index e84c116f12..a38d32cbbd 100644 --- a/docs_src/query_params_str_validations/tutorial009_py310.py +++ b/docs_src/query_params_str_validations/tutorial009_py310.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str | None = Query(None, alias="item-query")): +async def read_items(q: str | None = Query(default=None, alias="item-query")): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial010.py b/docs_src/query_params_str_validations/tutorial010.py index 7921506b65..35443d1947 100644 --- a/docs_src/query_params_str_validations/tutorial010.py +++ b/docs_src/query_params_str_validations/tutorial010.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -7,8 +7,8 @@ app = FastAPI() @app.get("/items/") async def read_items( - q: Optional[str] = Query( - None, + q: Union[str, None] = Query( + default=None, alias="item-query", title="Query string", description="Query string for the items to search in the database that have a good match", 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 0000000000..8995f3f57a --- /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 0000000000..cfa81926cf --- /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 0000000000..220eaabf47 --- /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/tutorial010_py310.py b/docs_src/query_params_str_validations/tutorial010_py310.py index c35800858d..f2839516e6 100644 --- a/docs_src/query_params_str_validations/tutorial010_py310.py +++ b/docs_src/query_params_str_validations/tutorial010_py310.py @@ -7,7 +7,7 @@ app = FastAPI() async def read_items( q: str | None = Query( - None, + default=None, alias="item-query", title="Query string", description="Query string for the items to search in the database that have a good match", diff --git a/docs_src/query_params_str_validations/tutorial011.py b/docs_src/query_params_str_validations/tutorial011.py index 7fda267edd..65bbce781a 100644 --- a/docs_src/query_params_str_validations/tutorial011.py +++ b/docs_src/query_params_str_validations/tutorial011.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI, Query @@ -6,6 +6,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Optional[List[str]] = Query(None)): +async def read_items(q: Union[List[str], None] = Query(default=None)): query_items = {"q": q} return query_items 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 0000000000..8ed699337d --- /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 0000000000..5dad4bfde1 --- /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 0000000000..416e3990dc --- /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/tutorial011_py310.py b/docs_src/query_params_str_validations/tutorial011_py310.py index c3d992e625..70155de7c9 100644 --- a/docs_src/query_params_str_validations/tutorial011_py310.py +++ b/docs_src/query_params_str_validations/tutorial011_py310.py @@ -4,6 +4,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: list[str] | None = Query(None)): +async def read_items(q: list[str] | None = Query(default=None)): query_items = {"q": q} return query_items diff --git a/docs_src/query_params_str_validations/tutorial011_py39.py b/docs_src/query_params_str_validations/tutorial011_py39.py index 38ba764d6f..878f95c798 100644 --- a/docs_src/query_params_str_validations/tutorial011_py39.py +++ b/docs_src/query_params_str_validations/tutorial011_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -6,6 +6,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Optional[list[str]] = Query(None)): +async def read_items(q: Union[list[str], None] = Query(default=None)): query_items = {"q": q} return query_items diff --git a/docs_src/query_params_str_validations/tutorial012.py b/docs_src/query_params_str_validations/tutorial012.py index 7ea9f017df..e77d56974d 100644 --- a/docs_src/query_params_str_validations/tutorial012.py +++ b/docs_src/query_params_str_validations/tutorial012.py @@ -6,6 +6,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: List[str] = Query(["foo", "bar"])): +async def read_items(q: List[str] = Query(default=["foo", "bar"])): 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 0000000000..261af250a1 --- /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 0000000000..9b5a9c2fb2 --- /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/tutorial012_py39.py b/docs_src/query_params_str_validations/tutorial012_py39.py index 1900133d9f..070d0b04bf 100644 --- a/docs_src/query_params_str_validations/tutorial012_py39.py +++ b/docs_src/query_params_str_validations/tutorial012_py39.py @@ -4,6 +4,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: list[str] = Query(["foo", "bar"])): +async def read_items(q: list[str] = Query(default=["foo", "bar"])): query_items = {"q": q} return query_items diff --git a/docs_src/query_params_str_validations/tutorial013.py b/docs_src/query_params_str_validations/tutorial013.py index 95dd6999dd..0b0f44869f 100644 --- a/docs_src/query_params_str_validations/tutorial013.py +++ b/docs_src/query_params_str_validations/tutorial013.py @@ -4,6 +4,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: list = Query([])): +async def read_items(q: list = Query(default=[])): 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 0000000000..f12a250559 --- /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 0000000000..602734145d --- /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.py b/docs_src/query_params_str_validations/tutorial014.py index fb50bc27b5..50e0a6c2b1 100644 --- a/docs_src/query_params_str_validations/tutorial014.py +++ b/docs_src/query_params_str_validations/tutorial014.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, Query @@ -7,7 +7,7 @@ app = FastAPI() @app.get("/items/") async def read_items( - hidden_query: Optional[str] = Query(None, include_in_schema=False) + hidden_query: Union[str, None] = Query(default=None, include_in_schema=False) ): if hidden_query: return {"hidden_query": hidden_query} 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 0000000000..a9a9c44270 --- /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 0000000000..5fba54150b --- /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 0000000000..b079852100 --- /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/query_params_str_validations/tutorial014_py310.py b/docs_src/query_params_str_validations/tutorial014_py310.py index 7ae39c7f97..1b617efdd1 100644 --- a/docs_src/query_params_str_validations/tutorial014_py310.py +++ b/docs_src/query_params_str_validations/tutorial014_py310.py @@ -4,7 +4,9 @@ app = FastAPI() @app.get("/items/") -async def read_items(hidden_query: str | None = Query(None, include_in_schema=False)): +async def read_items( + hidden_query: str | None = Query(default=None, include_in_schema=False) +): if hidden_query: return {"hidden_query": hidden_query} else: diff --git a/docs_src/request_files/tutorial001.py b/docs_src/request_files/tutorial001.py index 0fb1dd571b..2e0ea63912 100644 --- a/docs_src/request_files/tutorial001.py +++ b/docs_src/request_files/tutorial001.py @@ -4,7 +4,7 @@ app = FastAPI() @app.post("/files/") -async def create_file(file: bytes = File(...)): +async def create_file(file: bytes = File()): return {"file_size": len(file)} diff --git a/docs_src/request_files/tutorial001_02.py b/docs_src/request_files/tutorial001_02.py index 26a4c9cbf0..ac30be2d30 100644 --- a/docs_src/request_files/tutorial001_02.py +++ b/docs_src/request_files/tutorial001_02.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI, File, UploadFile @@ -6,7 +6,7 @@ app = FastAPI() @app.post("/files/") -async def create_file(file: Optional[bytes] = File(None)): +async def create_file(file: Union[bytes, None] = File(default=None)): if not file: return {"message": "No file sent"} else: @@ -14,7 +14,7 @@ async def create_file(file: Optional[bytes] = File(None)): @app.post("/uploadfile/") -async def create_upload_file(file: Optional[UploadFile] = None): +async def create_upload_file(file: Union[UploadFile, None] = None): if not file: return {"message": "No upload file sent"} else: 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 0000000000..5007fef159 --- /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 0000000000..9b80321b45 --- /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 0000000000..bb090ff6ca --- /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_02_py310.py b/docs_src/request_files/tutorial001_02_py310.py index 0e576251b5..298c9974f2 100644 --- a/docs_src/request_files/tutorial001_02_py310.py +++ b/docs_src/request_files/tutorial001_02_py310.py @@ -4,7 +4,7 @@ app = FastAPI() @app.post("/files/") -async def create_file(file: bytes | None = File(None)): +async def create_file(file: bytes | None = File(default=None)): if not file: return {"message": "No file sent"} else: diff --git a/docs_src/request_files/tutorial001_03.py b/docs_src/request_files/tutorial001_03.py index abcac9e4c1..d8005cc7d2 100644 --- a/docs_src/request_files/tutorial001_03.py +++ b/docs_src/request_files/tutorial001_03.py @@ -4,12 +4,12 @@ app = FastAPI() @app.post("/files/") -async def create_file(file: bytes = File(..., description="A file read as bytes")): +async def create_file(file: bytes = File(description="A file read as bytes")): return {"file_size": len(file)} @app.post("/uploadfile/") async def create_upload_file( - file: UploadFile = File(..., description="A file read as UploadFile") + file: UploadFile = File(description="A file read as UploadFile"), ): 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 0000000000..8a6b0a2455 --- /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 0000000000..93098a677a --- /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 0000000000..ca2f76d5c6 --- /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 0000000000..26a7672216 --- /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.py b/docs_src/request_files/tutorial002.py index 94abb7c6c0..b4d0acc68f 100644 --- a/docs_src/request_files/tutorial002.py +++ b/docs_src/request_files/tutorial002.py @@ -7,7 +7,7 @@ app = FastAPI() @app.post("/files/") -async def create_files(files: List[bytes] = File(...)): +async def create_files(files: List[bytes] = File()): return {"file_sizes": [len(file) for file in files]} diff --git a/docs_src/request_files/tutorial002_an.py b/docs_src/request_files/tutorial002_an.py new file mode 100644 index 0000000000..eaa90da2b6 --- /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 0000000000..db524ceab6 --- /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/tutorial002_py39.py b/docs_src/request_files/tutorial002_py39.py index 2779618bde..b64cf55987 100644 --- a/docs_src/request_files/tutorial002_py39.py +++ b/docs_src/request_files/tutorial002_py39.py @@ -5,7 +5,7 @@ app = FastAPI() @app.post("/files/") -async def create_files(files: list[bytes] = File(...)): +async def create_files(files: list[bytes] = File()): return {"file_sizes": [len(file) for file in files]} diff --git a/docs_src/request_files/tutorial003.py b/docs_src/request_files/tutorial003.py index 4a91b7a8bc..e3f805f605 100644 --- a/docs_src/request_files/tutorial003.py +++ b/docs_src/request_files/tutorial003.py @@ -8,14 +8,14 @@ app = FastAPI() @app.post("/files/") async def create_files( - files: List[bytes] = File(..., description="Multiple files as bytes") + files: 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: List[UploadFile] = File(..., description="Multiple files as UploadFile") + files: List[UploadFile] = File(description="Multiple files as UploadFile"), ): return {"filenames": [file.filename for file in files]} diff --git a/docs_src/request_files/tutorial003_an.py b/docs_src/request_files/tutorial003_an.py new file mode 100644 index 0000000000..2238e3c94b --- /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 0000000000..5a8c5dab5c --- /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_files/tutorial003_py39.py b/docs_src/request_files/tutorial003_py39.py index d853f48d11..96f5e8742d 100644 --- a/docs_src/request_files/tutorial003_py39.py +++ b/docs_src/request_files/tutorial003_py39.py @@ -6,14 +6,14 @@ app = FastAPI() @app.post("/files/") async def create_files( - files: list[bytes] = File(..., description="Multiple files as bytes") + files: 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: list[UploadFile] = File(..., description="Multiple files as UploadFile") + files: list[UploadFile] = File(description="Multiple files as UploadFile"), ): return {"filenames": [file.filename for file in files]} diff --git a/docs_src/request_forms/tutorial001.py b/docs_src/request_forms/tutorial001.py index c07e229458..a537700019 100644 --- a/docs_src/request_forms/tutorial001.py +++ b/docs_src/request_forms/tutorial001.py @@ -4,5 +4,5 @@ app = FastAPI() @app.post("/login/") -async def login(username: str = Form(...), password: str = Form(...)): +async def login(username: str = Form(), password: str = Form()): return {"username": username} diff --git a/docs_src/request_forms/tutorial001_an.py b/docs_src/request_forms/tutorial001_an.py new file mode 100644 index 0000000000..677fbf2db8 --- /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 0000000000..8e9d2ea53a --- /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.py b/docs_src/request_forms_and_files/tutorial001.py index 5bf3a5bc05..7b5224ce53 100644 --- a/docs_src/request_forms_and_files/tutorial001.py +++ b/docs_src/request_forms_and_files/tutorial001.py @@ -5,7 +5,7 @@ app = FastAPI() @app.post("/files/") async def create_file( - file: bytes = File(...), fileb: UploadFile = File(...), token: str = Form(...) + file: bytes = File(), fileb: UploadFile = File(), token: str = Form() ): return { "file_size": len(file), 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 0000000000..0ea285ac87 --- /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 0000000000..12cc43e50a --- /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_directly/tutorial001.py b/docs_src/response_directly/tutorial001.py index 6acdc0fc8d..5ab655a8a7 100644 --- a/docs_src/response_directly/tutorial001.py +++ b/docs_src/response_directly/tutorial001.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Optional +from typing import Union from fastapi import FastAPI from fastapi.encoders import jsonable_encoder @@ -10,7 +10,7 @@ from pydantic import BaseModel class Item(BaseModel): title: str timestamp: datetime - description: Optional[str] = None + description: Union[str, None] = None app = FastAPI() diff --git a/docs_src/response_model/tutorial001.py b/docs_src/response_model/tutorial001.py index 57992ecfc5..fd1c902a52 100644 --- a/docs_src/response_model/tutorial001.py +++ b/docs_src/response_model/tutorial001.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Any, List, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,12 +8,20 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: List[str] = [] @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 0000000000..98d30d540f --- /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 0000000000..7951c10761 --- /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 0000000000..16c78aa3fd --- /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 59efecde41..f8a2aa9fca 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 37b866864d..261e252d00 100644 --- a/docs_src/response_model/tutorial001_py39.py +++ b/docs_src/response_model/tutorial001_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Any, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,12 +8,20 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None tags: list[str] = [] @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 373317eb93..a58668f9ef 100644 --- a/docs_src/response_model/tutorial002.py +++ b/docs_src/response_model/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel, EmailStr @@ -10,10 +10,10 @@ class UserIn(BaseModel): username: str password: str email: EmailStr - full_name: Optional[str] = None + full_name: Union[str, None] = None # 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 29ab9c9d25..0a91a5967f 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 e14026dd8c..c42dbc7077 100644 --- a/docs_src/response_model/tutorial003.py +++ b/docs_src/response_model/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Any, Union from fastapi import FastAPI from pydantic import BaseModel, EmailStr @@ -10,15 +10,15 @@ class UserIn(BaseModel): username: str password: str email: EmailStr - full_name: Optional[str] = None + full_name: Union[str, None] = None class UserOut(BaseModel): username: str email: EmailStr - full_name: Optional[str] = None + full_name: Union[str, None] = None @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 0000000000..52694b5510 --- /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 0000000000..6ffddfd0af --- /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 0000000000..df6a09646d --- /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 0000000000..0d4bd8de57 --- /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 0000000000..b13a926929 --- /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 0000000000..cee49b83e0 --- /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 0000000000..0962061a60 --- /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 0000000000..f1c0f8e126 --- /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 fc9693e3cd..3703bf888a 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/response_model/tutorial004.py b/docs_src/response_model/tutorial004.py index 1e18f989dc..10b48039ae 100644 --- a/docs_src/response_model/tutorial004.py +++ b/docs_src/response_model/tutorial004.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,7 +8,7 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float tax: float = 10.5 tags: List[str] = [] diff --git a/docs_src/response_model/tutorial004_py39.py b/docs_src/response_model/tutorial004_py39.py index 07ccbbf416..9463b45ec1 100644 --- a/docs_src/response_model/tutorial004_py39.py +++ b/docs_src/response_model/tutorial004_py39.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,7 +8,7 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float tax: float = 10.5 tags: list[str] = [] diff --git a/docs_src/response_model/tutorial005.py b/docs_src/response_model/tutorial005.py index 03933d1f71..30eb9f8e3f 100644 --- a/docs_src/response_model/tutorial005.py +++ b/docs_src/response_model/tutorial005.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,7 +8,7 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float tax: float = 10.5 diff --git a/docs_src/response_model/tutorial006.py b/docs_src/response_model/tutorial006.py index 629ab8a3aa..3ffdb512bc 100644 --- a/docs_src/response_model/tutorial006.py +++ b/docs_src/response_model/tutorial006.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,7 +8,7 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float tax: float = 10.5 diff --git a/docs_src/schema_extra_example/tutorial001.py b/docs_src/schema_extra_example/tutorial001.py index fab4d7a445..a5ae281274 100644 --- a/docs_src/schema_extra_example/tutorial001.py +++ b/docs_src/schema_extra_example/tutorial001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None class Config: schema_extra = { diff --git a/docs_src/schema_extra_example/tutorial002.py b/docs_src/schema_extra_example/tutorial002.py index df3df88547..6de434f81d 100644 --- a/docs_src/schema_extra_example/tutorial002.py +++ b/docs_src/schema_extra_example/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import FastAPI from pydantic import BaseModel, Field @@ -7,10 +7,10 @@ app = FastAPI() class Item(BaseModel): - name: str = Field(..., example="Foo") - description: Optional[str] = Field(None, example="A very nice Item") - price: float = Field(..., example=35.4) - tax: Optional[float] = Field(None, example=3.2) + name: str = Field(example="Foo") + description: Union[str, None] = Field(default=None, example="A very nice Item") + price: float = Field(example=35.4) + tax: Union[float, None] = Field(default=None, example=3.2) @app.put("/items/{item_id}") diff --git a/docs_src/schema_extra_example/tutorial002_py310.py b/docs_src/schema_extra_example/tutorial002_py310.py index 4f8f8304ec..e84928bb11 100644 --- a/docs_src/schema_extra_example/tutorial002_py310.py +++ b/docs_src/schema_extra_example/tutorial002_py310.py @@ -5,10 +5,10 @@ app = FastAPI() class Item(BaseModel): - name: str = Field(..., example="Foo") - description: str | None = Field(None, example="A very nice Item") - price: float = Field(..., example=35.4) - tax: float | None = Field(None, example=3.2) + name: str = Field(example="Foo") + description: str | None = Field(default=None, example="A very nice Item") + price: float = Field(example=35.4) + tax: float | None = Field(default=None, example=3.2) @app.put("/items/{item_id}") diff --git a/docs_src/schema_extra_example/tutorial003.py b/docs_src/schema_extra_example/tutorial003.py index 58c79f5543..ce1736bbaf 100644 --- a/docs_src/schema_extra_example/tutorial003.py +++ b/docs_src/schema_extra_example/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel @@ -8,16 +8,15 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None @app.put("/items/{item_id}") async def update_item( item_id: int, item: Item = Body( - ..., example={ "name": "Foo", "description": "A very nice Item", 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 0000000000..1dec555a94 --- /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 0000000000..9edaddfb8e --- /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 0000000000..fe08847d98 --- /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/tutorial003_py310.py b/docs_src/schema_extra_example/tutorial003_py310.py index cf4c99dc03..1e137101d9 100644 --- a/docs_src/schema_extra_example/tutorial003_py310.py +++ b/docs_src/schema_extra_example/tutorial003_py310.py @@ -15,7 +15,6 @@ class Item(BaseModel): async def update_item( item_id: int, item: Item = Body( - ..., example={ "name": "Foo", "description": "A very nice Item", diff --git a/docs_src/schema_extra_example/tutorial004.py b/docs_src/schema_extra_example/tutorial004.py index 9f0e8b4378..b67edf30cd 100644 --- a/docs_src/schema_extra_example/tutorial004.py +++ b/docs_src/schema_extra_example/tutorial004.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Body, FastAPI from pydantic import BaseModel @@ -8,9 +8,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Optional[str] = None + description: Union[str, None] = None price: float - tax: Optional[float] = None + tax: Union[float, None] = None @app.put("/items/{item_id}") @@ -18,7 +18,6 @@ async def update_item( *, item_id: int, item: Item = Body( - ..., examples={ "normal": { "summary": "A normal example", 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 0000000000..82c9a92ac1 --- /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 0000000000..01f1a486cb --- /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 0000000000..d50e8aa5f8 --- /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/schema_extra_example/tutorial004_py310.py b/docs_src/schema_extra_example/tutorial004_py310.py index 6f29c1a5c6..100a30860b 100644 --- a/docs_src/schema_extra_example/tutorial004_py310.py +++ b/docs_src/schema_extra_example/tutorial004_py310.py @@ -16,7 +16,6 @@ async def update_item( *, item_id: int, item: Item = Body( - ..., examples={ "normal": { "summary": "A normal example", diff --git a/docs_src/security/tutorial001_an.py b/docs_src/security/tutorial001_an.py new file mode 100644 index 0000000000..dac915b7ca --- /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 0000000000..de110402ef --- /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.py b/docs_src/security/tutorial002.py index 03e0cd5fce..bfd035221c 100644 --- a/docs_src/security/tutorial002.py +++ b/docs_src/security/tutorial002.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI from fastapi.security import OAuth2PasswordBearer @@ -11,9 +11,9 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") class User(BaseModel): username: str - email: Optional[str] = None - full_name: Optional[str] = None - disabled: Optional[bool] = None + email: Union[str, None] = None + full_name: Union[str, None] = None + disabled: Union[bool, None] = None def fake_decode_token(token): diff --git a/docs_src/security/tutorial002_an.py b/docs_src/security/tutorial002_an.py new file mode 100644 index 0000000000..291b3bf530 --- /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 0000000000..c7b761e459 --- /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 0000000000..7ff1c470bb --- /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.py b/docs_src/security/tutorial003.py index a6bb176e4f..4b324866f6 100644 --- a/docs_src/security/tutorial003.py +++ b/docs_src/security/tutorial003.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm @@ -33,9 +33,9 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") class User(BaseModel): username: str - email: Optional[str] = None - full_name: Optional[str] = None - disabled: Optional[bool] = None + email: Union[str, None] = None + full_name: Union[str, None] = None + disabled: Union[bool, None] = None class UserInDB(User): diff --git a/docs_src/security/tutorial003_an.py b/docs_src/security/tutorial003_an.py new file mode 100644 index 0000000000..261cb4857d --- /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 0000000000..a03f4f8bf5 --- /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 0000000000..308dbe798f --- /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.py b/docs_src/security/tutorial004.py index 18e2c428fb..64099abe9c 100644 --- a/docs_src/security/tutorial004.py +++ b/docs_src/security/tutorial004.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm @@ -31,14 +31,14 @@ class Token(BaseModel): class TokenData(BaseModel): - username: Optional[str] = None + username: Union[str, None] = None class User(BaseModel): username: str - email: Optional[str] = None - full_name: Optional[str] = None - disabled: Optional[bool] = None + email: Union[str, None] = None + full_name: Union[str, None] = None + disabled: Union[bool, None] = None class UserInDB(User): @@ -75,7 +75,7 @@ def authenticate_user(fake_db, username: str, password: str): return user -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): +def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py new file mode 100644 index 0000000000..ca350343d2 --- /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 0000000000..8bf5f3b718 --- /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 0000000000..a634e23de9 --- /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.py b/docs_src/security/tutorial005.py index 5b34a09f11..bd0a33581c 100644 --- a/docs_src/security/tutorial005.py +++ b/docs_src/security/tutorial005.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import List, Optional +from typing import List, Union from fastapi import Depends, FastAPI, HTTPException, Security, status from fastapi.security import ( @@ -42,15 +42,15 @@ class Token(BaseModel): class TokenData(BaseModel): - username: Optional[str] = None + username: Union[str, None] = None scopes: List[str] = [] class User(BaseModel): username: str - email: Optional[str] = None - full_name: Optional[str] = None - disabled: Optional[bool] = None + email: Union[str, None] = None + full_name: Union[str, None] = None + disabled: Union[bool, None] = None class UserInDB(User): @@ -90,7 +90,7 @@ def authenticate_user(fake_db, username: str, password: str): return user -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): +def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta @@ -107,7 +107,7 @@ async def get_current_user( if security_scopes.scopes: authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' else: - authenticate_value = f"Bearer" + authenticate_value = "Bearer" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py new file mode 100644 index 0000000000..ec4fa1a07e --- /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 0000000000..45f3fc0bd6 --- /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 0000000000..ecb5ed5160 --- /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/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py index c6a095d2cc..ba756ef4f4 100644 --- a/docs_src/security/tutorial005_py310.py +++ b/docs_src/security/tutorial005_py310.py @@ -106,7 +106,7 @@ async def get_current_user( if security_scopes.scopes: authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' else: - authenticate_value = f"Bearer" + authenticate_value = "Bearer" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py index d45c08ce6f..9e4dbcffba 100644 --- a/docs_src/security/tutorial005_py39.py +++ b/docs_src/security/tutorial005_py39.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Optional +from typing import Union from fastapi import Depends, FastAPI, HTTPException, Security, status from fastapi.security import ( @@ -42,15 +42,15 @@ class Token(BaseModel): class TokenData(BaseModel): - username: Optional[str] = None + username: Union[str, None] = None scopes: list[str] = [] class User(BaseModel): username: str - email: Optional[str] = None - full_name: Optional[str] = None - disabled: Optional[bool] = None + email: Union[str, None] = None + full_name: Union[str, None] = None + disabled: Union[bool, None] = None class UserInDB(User): @@ -90,7 +90,7 @@ def authenticate_user(fake_db, username: str, password: str): return user -def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): +def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta @@ -107,7 +107,7 @@ async def get_current_user( if security_scopes.scopes: authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' else: - authenticate_value = f"Bearer" + authenticate_value = "Bearer" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", diff --git a/docs_src/security/tutorial006_an.py b/docs_src/security/tutorial006_an.py new file mode 100644 index 0000000000..985e4b2ad2 --- /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 0000000000..03c696a4b6 --- /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.py b/docs_src/security/tutorial007.py index 90b9ac0546..790ee10bc6 100644 --- a/docs_src/security/tutorial007.py +++ b/docs_src/security/tutorial007.py @@ -9,9 +9,17 @@ security = HTTPBasic() def get_current_username(credentials: HTTPBasicCredentials = Depends(security)): - correct_username = secrets.compare_digest(credentials.username, "stanleyjobson") - correct_password = secrets.compare_digest(credentials.password, "swordfish") - if not (correct_username and correct_password): + 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", diff --git a/docs_src/security/tutorial007_an.py b/docs_src/security/tutorial007_an.py new file mode 100644 index 0000000000..5fb7c8e575 --- /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 0000000000..17177dabf9 --- /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/pl/overrides/.gitignore b/docs_src/settings/app02_an/__init__.py similarity index 100% rename from docs/pl/overrides/.gitignore rename to docs_src/settings/app02_an/__init__.py diff --git a/docs_src/settings/app02_an/config.py b/docs_src/settings/app02_an/config.py new file mode 100644 index 0000000000..9a78291356 --- /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 0000000000..cb679202d6 --- /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 0000000000..7a04d7e8ee --- /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/pt/overrides/.gitignore b/docs_src/settings/app02_an_py39/__init__.py similarity index 100% rename from docs/pt/overrides/.gitignore rename to docs_src/settings/app02_an_py39/__init__.py 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 0000000000..9a78291356 --- /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 0000000000..61be74fcb9 --- /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 0000000000..7a04d7e8ee --- /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/ru/overrides/.gitignore b/docs_src/settings/app03_an/__init__.py similarity index 100% rename from docs/ru/overrides/.gitignore rename to docs_src/settings/app03_an/__init__.py diff --git a/docs_src/settings/app03_an/config.py b/docs_src/settings/app03_an/config.py new file mode 100644 index 0000000000..e1c3ee3006 --- /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 0000000000..c33b98f474 --- /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/sq/overrides/.gitignore b/docs_src/settings/app03_an_py39/__init__.py similarity index 100% rename from docs/sq/overrides/.gitignore rename to docs_src/settings/app03_an_py39/__init__.py 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 0000000000..e1c3ee3006 --- /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 0000000000..b89c6b6cf4 --- /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/sql_databases/sql_app/schemas.py b/docs_src/sql_databases/sql_app/schemas.py index 51655663a0..c49beba882 100644 --- a/docs_src/sql_databases/sql_app/schemas.py +++ b/docs_src/sql_databases/sql_app/schemas.py @@ -1,11 +1,11 @@ -from typing import List, Optional +from typing import List, Union from pydantic import BaseModel class ItemBase(BaseModel): title: str - description: Optional[str] = None + description: Union[str, None] = None class ItemCreate(ItemBase): diff --git a/docs_src/sql_databases/sql_app/tests/test_sql_app.py b/docs_src/sql_databases/sql_app/tests/test_sql_app.py index c60c3356f8..5f55add0a9 100644 --- a/docs_src/sql_databases/sql_app/tests/test_sql_app.py +++ b/docs_src/sql_databases/sql_app/tests/test_sql_app.py @@ -1,14 +1,17 @@ from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import StaticPool from ..database import Base from ..main import app, get_db -SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" +SQLALCHEMY_DATABASE_URL = "sqlite://" engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} + SQLALCHEMY_DATABASE_URL, + connect_args={"check_same_thread": False}, + poolclass=StaticPool, ) TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/docs_src/sql_databases/sql_app_py39/schemas.py b/docs_src/sql_databases/sql_app_py39/schemas.py index a19f1cdfec..dadc403d93 100644 --- a/docs_src/sql_databases/sql_app_py39/schemas.py +++ b/docs_src/sql_databases/sql_app_py39/schemas.py @@ -1,11 +1,11 @@ -from typing import Optional +from typing import Union from pydantic import BaseModel class ItemBase(BaseModel): title: str - description: Optional[str] = None + description: Union[str, None] = None class ItemCreate(ItemBase): diff --git a/docs_src/sql_databases_peewee/sql_app/schemas.py b/docs_src/sql_databases_peewee/sql_app/schemas.py index b715604eec..d8775cb30c 100644 --- a/docs_src/sql_databases_peewee/sql_app/schemas.py +++ b/docs_src/sql_databases_peewee/sql_app/schemas.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional +from typing import Any, List, Union import peewee from pydantic import BaseModel @@ -15,7 +15,7 @@ class PeeweeGetterDict(GetterDict): class ItemBase(BaseModel): title: str - description: Optional[str] = None + description: Union[str, None] = None class ItemCreate(ItemBase): diff --git a/docs_src/websockets/tutorial002.py b/docs_src/websockets/tutorial002.py index 53cdb41ff1..cab749e4db 100644 --- a/docs_src/websockets/tutorial002.py +++ b/docs_src/websockets/tutorial002.py @@ -1,6 +1,14 @@ -from typing import Optional +from typing import Union -from fastapi import Cookie, Depends, FastAPI, Query, WebSocket, status +from fastapi import ( + Cookie, + Depends, + FastAPI, + Query, + WebSocket, + WebSocketException, + status, +) from fastapi.responses import HTMLResponse app = FastAPI() @@ -57,11 +65,11 @@ async def get(): async def get_cookie_or_token( websocket: WebSocket, - session: Optional[str] = Cookie(None), - token: Optional[str] = Query(None), + session: Union[str, None] = Cookie(default=None), + token: Union[str, None] = Query(default=None), ): if session is None and token is None: - await websocket.close(code=status.WS_1008_POLICY_VIOLATION) + raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) return session or token @@ -69,7 +77,7 @@ async def get_cookie_or_token( async def websocket_endpoint( websocket: WebSocket, item_id: str, - q: Optional[int] = None, + q: Union[int, None] = None, cookie_or_token: str = Depends(get_cookie_or_token), ): await websocket.accept() diff --git a/docs_src/websockets/tutorial002_an.py b/docs_src/websockets/tutorial002_an.py new file mode 100644 index 0000000000..c838fbd306 --- /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 = """ + + + + Chat + + +

WebSocket Chat

+
+ + + +
+ + +
+
    +
+ + + +""" + + +@app.get("/") +async def get(): + return HTMLResponse(html) + + +async def get_cookie_or_token( + websocket: WebSocket, + session: Annotated[Union[str, None], Cookie()] = None, + token: Annotated[Union[str, None], Query()] = None, +): + if session is None and token is None: + raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) + return session or token + + +@app.websocket("/items/{item_id}/ws") +async def websocket_endpoint( + *, + websocket: WebSocket, + item_id: str, + q: Union[int, None] = None, + cookie_or_token: Annotated[str, Depends(get_cookie_or_token)], +): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text( + f"Session cookie or query token value is: {cookie_or_token}" + ) + if q is not None: + await websocket.send_text(f"Query parameter q is: {q}") + await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") diff --git a/docs_src/websockets/tutorial002_an_py310.py b/docs_src/websockets/tutorial002_an_py310.py new file mode 100644 index 0000000000..551539b32e --- /dev/null +++ b/docs_src/websockets/tutorial002_an_py310.py @@ -0,0 +1,92 @@ +from typing import Annotated + +from fastapi import ( + Cookie, + Depends, + FastAPI, + Query, + WebSocket, + WebSocketException, + status, +) +from fastapi.responses import HTMLResponse + +app = FastAPI() + +html = """ + + + + Chat + + +

WebSocket Chat

+
+ + + +
+ + +
+
    +
+ + + +""" + + +@app.get("/") +async def get(): + return HTMLResponse(html) + + +async def get_cookie_or_token( + websocket: WebSocket, + session: Annotated[str | None, Cookie()] = None, + token: Annotated[str | None, Query()] = None, +): + if session is None and token is None: + raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) + return session or token + + +@app.websocket("/items/{item_id}/ws") +async def websocket_endpoint( + *, + websocket: WebSocket, + item_id: str, + q: int | None = None, + cookie_or_token: Annotated[str, Depends(get_cookie_or_token)], +): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text( + f"Session cookie or query token value is: {cookie_or_token}" + ) + if q is not None: + await websocket.send_text(f"Query parameter q is: {q}") + await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") diff --git a/docs_src/websockets/tutorial002_an_py39.py b/docs_src/websockets/tutorial002_an_py39.py new file mode 100644 index 0000000000..606d355fe1 --- /dev/null +++ b/docs_src/websockets/tutorial002_an_py39.py @@ -0,0 +1,92 @@ +from typing import Annotated, Union + +from fastapi import ( + Cookie, + Depends, + FastAPI, + Query, + WebSocket, + WebSocketException, + status, +) +from fastapi.responses import HTMLResponse + +app = FastAPI() + +html = """ + + + + Chat + + +

WebSocket Chat

+
+ + + +
+ + +
+
    +
+ + + +""" + + +@app.get("/") +async def get(): + return HTMLResponse(html) + + +async def get_cookie_or_token( + websocket: WebSocket, + session: Annotated[Union[str, None], Cookie()] = None, + token: Annotated[Union[str, None], Query()] = None, +): + if session is None and token is None: + raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) + return session or token + + +@app.websocket("/items/{item_id}/ws") +async def websocket_endpoint( + *, + websocket: WebSocket, + item_id: str, + q: Union[int, None] = None, + cookie_or_token: Annotated[str, Depends(get_cookie_or_token)], +): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text( + f"Session cookie or query token value is: {cookie_or_token}" + ) + if q is not None: + await websocket.send_text(f"Query parameter q is: {q}") + await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") diff --git a/docs_src/websockets/tutorial002_py310.py b/docs_src/websockets/tutorial002_py310.py new file mode 100644 index 0000000000..51daf58e5a --- /dev/null +++ b/docs_src/websockets/tutorial002_py310.py @@ -0,0 +1,89 @@ +from fastapi import ( + Cookie, + Depends, + FastAPI, + Query, + WebSocket, + WebSocketException, + status, +) +from fastapi.responses import HTMLResponse + +app = FastAPI() + +html = """ + + + + Chat + + +

WebSocket Chat

+
+ + + +
+ + +
+
    +
+ + + +""" + + +@app.get("/") +async def get(): + return HTMLResponse(html) + + +async def get_cookie_or_token( + websocket: WebSocket, + session: str | None = Cookie(default=None), + token: str | None = Query(default=None), +): + if session is None and token is None: + raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) + return session or token + + +@app.websocket("/items/{item_id}/ws") +async def websocket_endpoint( + websocket: WebSocket, + item_id: str, + q: int | None = None, + cookie_or_token: str = Depends(get_cookie_or_token), +): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text( + f"Session cookie or query token value is: {cookie_or_token}" + ) + if q is not None: + await websocket.send_text(f"Query parameter q is: {q}") + await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") diff --git a/docs_src/websockets/tutorial003_py39.py b/docs_src/websockets/tutorial003_py39.py new file mode 100644 index 0000000000..3162180889 --- /dev/null +++ b/docs_src/websockets/tutorial003_py39.py @@ -0,0 +1,81 @@ +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse + +app = FastAPI() + +html = """ + + + + Chat + + +

WebSocket Chat

+

Your ID:

+
+ + +
+
    +
+ + + +""" + + +class ConnectionManager: + def __init__(self): + self.active_connections: list[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, websocket: WebSocket): + await websocket.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@app.get("/") +async def get(): + return HTMLResponse(html) + + +@app.websocket("/ws/{client_id}") +async def websocket_endpoint(websocket: WebSocket, client_id: int): + await manager.connect(websocket) + try: + while True: + data = await websocket.receive_text() + await manager.send_personal_message(f"You wrote: {data}", websocket) + await manager.broadcast(f"Client #{client_id} says: {data}") + except WebSocketDisconnect: + manager.disconnect(websocket) + await manager.broadcast(f"Client #{client_id} left the chat") diff --git a/docs_src/wsgi/tutorial001.py b/docs_src/wsgi/tutorial001.py index 500ecf883e..7f27a85a19 100644 --- a/docs_src/wsgi/tutorial001.py +++ b/docs_src/wsgi/tutorial001.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.wsgi import WSGIMiddleware -from flask import Flask, escape, request +from flask import Flask, request +from markupsafe import escape flask_app = Flask(__name__) diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 1a4d001620..038e1ba864 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.77.1" +__version__ = "0.98.0" from starlette import status as status @@ -8,6 +8,7 @@ from .applications import FastAPI as FastAPI from .background import BackgroundTasks as BackgroundTasks from .datastructures import UploadFile as UploadFile from .exceptions import HTTPException as HTTPException +from .exceptions import WebSocketException as WebSocketException from .param_functions import Body as Body from .param_functions import Cookie as Cookie from .param_functions import Depends as Depends diff --git a/fastapi/applications.py b/fastapi/applications.py index 7530ddb9b8..9b161c5ec8 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -9,6 +9,7 @@ from typing import ( Optional, Sequence, Type, + TypeVar, Union, ) @@ -18,8 +19,9 @@ from fastapi.encoders import DictIntStrAny, SetIntStr from fastapi.exception_handlers import ( http_exception_handler, request_validation_exception_handler, + websocket_request_validation_exception_handler, ) -from fastapi.exceptions import RequestValidationError +from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError from fastapi.logger import logger from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware from fastapi.openapi.docs import ( @@ -33,18 +35,22 @@ from fastapi.types import DecoratedCallable from fastapi.utils import generate_unique_id from starlette.applications import Starlette from starlette.datastructures import State -from starlette.exceptions import ExceptionMiddleware, HTTPException +from starlette.exceptions import HTTPException from starlette.middleware import Middleware +from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.errors import ServerErrorMiddleware +from starlette.middleware.exceptions import ExceptionMiddleware from starlette.requests import Request from starlette.responses import HTMLResponse, JSONResponse, Response from starlette.routing import BaseRoute -from starlette.types import ASGIApp, Receive, Scope, Send +from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send + +AppType = TypeVar("AppType", bound="FastAPI") class FastAPI(Starlette): def __init__( - self, + self: AppType, *, debug: bool = False, routes: Optional[List[BaseRoute]] = None, @@ -56,6 +62,7 @@ class FastAPI(Starlette): servers: Optional[List[Dict[str, Union[str, Any]]]] = None, dependencies: Optional[Sequence[Depends]] = None, default_response_class: Type[Response] = Default(JSONResponse), + redirect_slashes: bool = True, docs_url: Optional[str] = "/docs", redoc_url: Optional[str] = "/redoc", swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect", @@ -69,6 +76,7 @@ class FastAPI(Starlette): ] = None, on_startup: Optional[Sequence[Callable[[], Any]]] = None, on_shutdown: Optional[Sequence[Callable[[], Any]]] = None, + lifespan: Optional[Lifespan[AppType]] = None, terms_of_service: Optional[str] = None, contact: Optional[Dict[str, Union[str, Any]]] = None, license_info: Optional[Dict[str, Union[str, Any]]] = None, @@ -85,7 +93,7 @@ class FastAPI(Starlette): ), **extra: Any, ) -> None: - self._debug: bool = debug + self.debug = debug self.title = title self.description = description self.version = version @@ -120,9 +128,11 @@ class FastAPI(Starlette): self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {} self.router: routing.APIRouter = routing.APIRouter( routes=routes, + redirect_slashes=redirect_slashes, dependency_overrides_provider=self, on_startup=on_startup, on_shutdown=on_shutdown, + lifespan=lifespan, default_response_class=default_response_class, dependencies=dependencies, callbacks=callbacks, @@ -138,11 +148,16 @@ class FastAPI(Starlette): self.exception_handlers.setdefault( RequestValidationError, request_validation_exception_handler ) + self.exception_handlers.setdefault( + WebSocketRequestValidationError, + # Starlette still has incorrect type specification for the handlers + websocket_request_validation_exception_handler, # type: ignore + ) self.user_middleware: List[Middleware] = ( [] if middleware is None else list(middleware) ) - self.middleware_stack: ASGIApp = self.build_middleware_stack() + self.middleware_stack: Union[ASGIApp, None] = None self.setup() def build_middleware_stack(self) -> ASGIApp: @@ -273,7 +288,7 @@ class FastAPI(Starlette): path: str, endpoint: Callable[..., Coroutine[Any, Any, Response]], *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -331,7 +346,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -388,15 +403,34 @@ class FastAPI(Starlette): return decorator def add_api_websocket_route( - self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None + self, + path: str, + endpoint: Callable[..., Any], + name: Optional[str] = None, + *, + dependencies: Optional[Sequence[Depends]] = None, ) -> None: - self.router.add_api_websocket_route(path, endpoint, name=name) + self.router.add_api_websocket_route( + path, + endpoint, + name=name, + dependencies=dependencies, + ) def websocket( - self, path: str, name: Optional[str] = None + self, + path: str, + name: Optional[str] = None, + *, + dependencies: Optional[Sequence[Depends]] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_api_websocket_route(path, func, name=name) + self.add_api_websocket_route( + path, + func, + name=name, + dependencies=dependencies, + ) return func return decorator @@ -434,7 +468,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -489,7 +523,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -544,7 +578,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -599,7 +633,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -635,10 +669,10 @@ class FastAPI(Starlette): response_description=response_description, responses=responses, deprecated=deprecated, + operation_id=operation_id, response_model_include=response_model_include, response_model_exclude=response_model_exclude, response_model_by_alias=response_model_by_alias, - operation_id=operation_id, response_model_exclude_unset=response_model_exclude_unset, response_model_exclude_defaults=response_model_exclude_defaults, response_model_exclude_none=response_model_exclude_none, @@ -654,7 +688,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -709,7 +743,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -764,7 +798,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -819,7 +853,7 @@ class FastAPI(Starlette): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, @@ -869,3 +903,35 @@ class FastAPI(Starlette): openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, ) + + def websocket_route( + self, path: str, name: Union[str, None] = None + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.router.add_websocket_route(path, func, name=name) + return func + + return decorator + + def on_event( + self, event_type: str + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + return self.router.on_event(event_type) + + def middleware( + self, middleware_type: str + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_middleware(BaseHTTPMiddleware, dispatch=func) + return func + + return decorator + + def exception_handler( + self, exc_class_or_status_code: Union[int, Type[Exception]] + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_exception_handler(exc_class_or_status_code, func) + return func + + return decorator diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py index becac3f33d..31b878d5df 100644 --- a/fastapi/concurrency.py +++ b/fastapi/concurrency.py @@ -1,20 +1,15 @@ -import sys +from contextlib import AsyncExitStack as AsyncExitStack # noqa +from contextlib import asynccontextmanager as asynccontextmanager from typing import AsyncGenerator, ContextManager, TypeVar +import anyio +from anyio import CapacityLimiter from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa from starlette.concurrency import ( # noqa run_until_first_complete as run_until_first_complete, ) -if sys.version_info >= (3, 7): - from contextlib import AsyncExitStack as AsyncExitStack - from contextlib import asynccontextmanager as asynccontextmanager -else: - from contextlib2 import AsyncExitStack as AsyncExitStack # noqa - from contextlib2 import asynccontextmanager as asynccontextmanager # noqa - - _T = TypeVar("_T") @@ -22,11 +17,24 @@ _T = TypeVar("_T") async def contextmanager_in_threadpool( cm: ContextManager[_T], ) -> AsyncGenerator[_T, None]: + # blocking __exit__ from running waiting on a free thread + # can create race conditions/deadlocks if the context manager itself + # has it's own internal pool (e.g. a database connection pool) + # to avoid this we let __exit__ run without a capacity limit + # since we're creating a new limiter for each call, any non-zero limit + # works (1 is arbitrary) + exit_limiter = CapacityLimiter(1) try: yield await run_in_threadpool(cm.__enter__) except Exception as e: - ok: bool = await run_in_threadpool(cm.__exit__, type(e), e, None) + ok = bool( + await anyio.to_thread.run_sync( + cm.__exit__, type(e), e, None, limiter=exit_limiter + ) + ) if not ok: raise e else: - await run_in_threadpool(cm.__exit__, None, None, None) + await anyio.to_thread.run_sync( + cm.__exit__, None, None, None, limiter=exit_limiter + ) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 9dccd354ef..f131001ce2 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,12 +1,13 @@ import dataclasses import inspect from contextlib import contextmanager -from copy import deepcopy +from copy import copy, deepcopy from typing import ( Any, Callable, Coroutine, Dict, + ForwardRef, List, Mapping, Optional, @@ -34,6 +35,7 @@ from pydantic import BaseModel, create_model from pydantic.error_wrappers import ErrorWrapper from pydantic.errors import MissingError from pydantic.fields import ( + SHAPE_FROZENSET, SHAPE_LIST, SHAPE_SEQUENCE, SHAPE_SET, @@ -43,9 +45,10 @@ from pydantic.fields import ( FieldInfo, ModelField, Required, + Undefined, ) from pydantic.schema import get_annotation_from_field_info -from pydantic.typing import ForwardRef, evaluate_forwardref +from pydantic.typing import evaluate_forwardref, get_args, get_origin from pydantic.utils import lenient_issubclass from starlette.background import BackgroundTasks from starlette.concurrency import run_in_threadpool @@ -53,10 +56,12 @@ from starlette.datastructures import FormData, Headers, QueryParams, UploadFile from starlette.requests import HTTPConnection, Request from starlette.responses import Response from starlette.websockets import WebSocket +from typing_extensions import Annotated sequence_shapes = { SHAPE_LIST, SHAPE_SET, + SHAPE_FROZENSET, SHAPE_TUPLE, SHAPE_SEQUENCE, SHAPE_TUPLE_ELLIPSIS, @@ -101,25 +106,25 @@ def check_file_field(field: ModelField) -> None: assert parse_options_header except ImportError: logger.error(multipart_incorrect_install_error) - raise RuntimeError(multipart_incorrect_install_error) + raise RuntimeError(multipart_incorrect_install_error) from None except ImportError: logger.error(multipart_not_installed_error) - raise RuntimeError(multipart_not_installed_error) + raise RuntimeError(multipart_not_installed_error) from None def get_param_sub_dependant( - *, param: inspect.Parameter, path: str, security_scopes: Optional[List[str]] = None + *, + param_name: str, + depends: params.Depends, + path: str, + security_scopes: Optional[List[str]] = None, ) -> Dependant: - depends: params.Depends = param.default - if depends.dependency: - dependency = depends.dependency - else: - dependency = param.annotation + assert depends.dependency return get_sub_dependant( depends=depends, - dependency=dependency, + dependency=depends.dependency, path=path, - name=param.name, + name=param_name, security_scopes=security_scopes, ) @@ -160,7 +165,6 @@ def get_sub_dependant( ) if security_requirement: sub_dependant.security_requirements.append(security_requirement) - sub_dependant.security_scopes = security_scopes return sub_dependant @@ -250,7 +254,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: name=param.name, kind=param.kind, default=param.default, - annotation=get_typed_annotation(param, globalns), + annotation=get_typed_annotation(param.annotation, globalns), ) for param in signature.parameters.values() ] @@ -258,14 +262,24 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: return typed_signature -def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any: - annotation = param.annotation +def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: if isinstance(annotation, str): annotation = ForwardRef(annotation) annotation = evaluate_forwardref(annotation, globalns, globalns) return annotation +def get_typed_return_annotation(call: Callable[..., Any]) -> Any: + signature = inspect.signature(call) + annotation = signature.return_annotation + + if annotation is inspect.Signature.empty: + return None + + globalns = getattr(call, "__globals__", {}) + return get_typed_annotation(annotation, globalns) + + def get_dependant( *, path: str, @@ -277,123 +291,208 @@ def get_dependant( path_param_names = get_path_param_names(path) endpoint_signature = get_typed_signature(call) signature_params = endpoint_signature.parameters - dependant = Dependant(call=call, name=name, path=path, use_cache=use_cache) + dependant = Dependant( + call=call, + name=name, + path=path, + security_scopes=security_scopes, + use_cache=use_cache, + ) for param_name, param in signature_params.items(): - if isinstance(param.default, params.Depends): + is_path_param = param_name in path_param_names + type_annotation, depends, param_field = analyze_param( + param_name=param_name, + annotation=param.annotation, + value=param.default, + is_path_param=is_path_param, + ) + if depends is not None: sub_dependant = get_param_sub_dependant( - param=param, path=path, security_scopes=security_scopes + param_name=param_name, + depends=depends, + path=path, + security_scopes=security_scopes, ) dependant.dependencies.append(sub_dependant) continue - if add_non_field_param_to_dependency(param=param, dependant=dependant): + if add_non_field_param_to_dependency( + param_name=param_name, + type_annotation=type_annotation, + dependant=dependant, + ): + assert ( + param_field is None + ), f"Cannot specify multiple FastAPI annotations for {param_name!r}" continue - param_field = get_param_field( - param=param, default_field_info=params.Query, param_name=param_name - ) - if param_name in path_param_names: - assert is_scalar_field( - field=param_field - ), "Path params must be of one of the supported types" - if isinstance(param.default, params.Path): - ignore_default = False - else: - ignore_default = True - param_field = get_param_field( - param=param, - param_name=param_name, - default_field_info=params.Path, - force_type=params.ParamTypes.path, - ignore_default=ignore_default, - ) - add_param_to_fields(field=param_field, dependant=dependant) - elif is_scalar_field(field=param_field): - add_param_to_fields(field=param_field, dependant=dependant) - elif isinstance( - param.default, (params.Query, params.Header) - ) and is_scalar_sequence_field(param_field): - add_param_to_fields(field=param_field, dependant=dependant) - else: - field_info = param_field.field_info - assert isinstance( - field_info, params.Body - ), f"Param: {param_field.name} can only be a request body, using Body(...)" + assert param_field is not None + if is_body_param(param_field=param_field, is_path_param=is_path_param): dependant.body_params.append(param_field) + else: + add_param_to_fields(field=param_field, dependant=dependant) return dependant def add_non_field_param_to_dependency( - *, param: inspect.Parameter, dependant: Dependant + *, param_name: str, type_annotation: Any, dependant: Dependant ) -> Optional[bool]: - if lenient_issubclass(param.annotation, Request): - dependant.request_param_name = param.name + if lenient_issubclass(type_annotation, Request): + dependant.request_param_name = param_name return True - elif lenient_issubclass(param.annotation, WebSocket): - dependant.websocket_param_name = param.name + elif lenient_issubclass(type_annotation, WebSocket): + dependant.websocket_param_name = param_name return True - elif lenient_issubclass(param.annotation, HTTPConnection): - dependant.http_connection_param_name = param.name + elif lenient_issubclass(type_annotation, HTTPConnection): + dependant.http_connection_param_name = param_name return True - elif lenient_issubclass(param.annotation, Response): - dependant.response_param_name = param.name + elif lenient_issubclass(type_annotation, Response): + dependant.response_param_name = param_name return True - elif lenient_issubclass(param.annotation, BackgroundTasks): - dependant.background_tasks_param_name = param.name + elif lenient_issubclass(type_annotation, BackgroundTasks): + dependant.background_tasks_param_name = param_name return True - elif lenient_issubclass(param.annotation, SecurityScopes): - dependant.security_scopes_param_name = param.name + elif lenient_issubclass(type_annotation, SecurityScopes): + dependant.security_scopes_param_name = param_name return True return None -def get_param_field( +def analyze_param( *, - param: inspect.Parameter, param_name: str, - default_field_info: Type[params.Param] = params.Param, - force_type: Optional[params.ParamTypes] = None, - ignore_default: bool = False, -) -> ModelField: - default_value = Required - had_schema = False - if not param.default == param.empty and ignore_default is False: - default_value = param.default - if isinstance(default_value, FieldInfo): - had_schema = True - field_info = default_value - default_value = field_info.default - if ( + annotation: Any, + value: Any, + is_path_param: bool, +) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]: + field_info = None + used_default_field_info = False + depends = None + type_annotation: Any = Any + if ( + annotation is not inspect.Signature.empty + and get_origin(annotation) is Annotated # type: ignore[comparison-overlap] + ): + annotated_args = get_args(annotation) + type_annotation = annotated_args[0] + fastapi_annotations = [ + arg + for arg in annotated_args[1:] + if isinstance(arg, (FieldInfo, params.Depends)) + ] + assert ( + len(fastapi_annotations) <= 1 + ), f"Cannot specify multiple `Annotated` FastAPI arguments for {param_name!r}" + fastapi_annotation = next(iter(fastapi_annotations), None) + if isinstance(fastapi_annotation, FieldInfo): + # Copy `field_info` because we mutate `field_info.default` below. + field_info = copy(fastapi_annotation) + assert field_info.default is Undefined or field_info.default is Required, ( + f"`{field_info.__class__.__name__}` default value cannot be set in" + f" `Annotated` for {param_name!r}. Set the default value with `=` instead." + ) + if value is not inspect.Signature.empty: + assert not is_path_param, "Path parameters cannot have default values" + field_info.default = value + else: + field_info.default = Required + elif isinstance(fastapi_annotation, params.Depends): + depends = fastapi_annotation + elif annotation is not inspect.Signature.empty: + type_annotation = annotation + + if isinstance(value, params.Depends): + assert depends is None, ( + "Cannot specify `Depends` in `Annotated` and default value" + f" together for {param_name!r}" + ) + assert field_info is None, ( + "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a" + f" default value together for {param_name!r}" + ) + depends = value + elif isinstance(value, FieldInfo): + assert field_info is None, ( + "Cannot specify FastAPI annotations in `Annotated` and default value" + f" together for {param_name!r}" + ) + field_info = value + + if depends is not None and depends.dependency is None: + depends.dependency = type_annotation + + if lenient_issubclass( + type_annotation, + (Request, WebSocket, HTTPConnection, Response, BackgroundTasks, SecurityScopes), + ): + assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}" + assert ( + field_info is None + ), f"Cannot specify FastAPI annotation for type {type_annotation!r}" + elif field_info is None and depends is None: + default_value = value if value is not inspect.Signature.empty else Required + if is_path_param: + # We might check here that `default_value is Required`, but the fact is that the same + # parameter might sometimes be a path parameter and sometimes not. See + # `tests/test_infer_param_optionality.py` for an example. + field_info = params.Path() + else: + field_info = params.Query(default=default_value) + used_default_field_info = True + + field = None + if field_info is not None: + if is_path_param: + assert isinstance(field_info, params.Path), ( + f"Cannot use `{field_info.__class__.__name__}` for path param" + f" {param_name!r}" + ) + elif ( isinstance(field_info, params.Param) and getattr(field_info, "in_", None) is None ): - field_info.in_ = default_field_info.in_ - if force_type: - field_info.in_ = force_type # type: ignore - else: - field_info = default_field_info(default_value) - required = default_value == Required - annotation: Any = Any - if not param.annotation == param.empty: - annotation = param.annotation - annotation = get_annotation_from_field_info(annotation, field_info, param_name) - if not field_info.alias and getattr(field_info, "convert_underscores", None): - alias = param.name.replace("_", "-") - else: - alias = field_info.alias or param.name - field = create_response_field( - name=param.name, - type_=annotation, - default=None if required else default_value, - alias=alias, - required=required, - field_info=field_info, - ) - field.required = required - if not had_schema and not is_scalar_field(field=field): - field.field_info = params.Body(field_info.default) - if not had_schema and lenient_issubclass(field.type_, UploadFile): - field.field_info = params.File(field_info.default) + field_info.in_ = params.ParamTypes.query + annotation = get_annotation_from_field_info( + annotation if annotation is not inspect.Signature.empty else Any, + field_info, + param_name, + ) + if not field_info.alias and getattr(field_info, "convert_underscores", None): + alias = param_name.replace("_", "-") + else: + alias = field_info.alias or param_name + field = create_response_field( + name=param_name, + type_=annotation, + default=field_info.default, + alias=alias, + required=field_info.default in (Required, Undefined), + field_info=field_info, + ) + if used_default_field_info: + if lenient_issubclass(field.type_, UploadFile): + field.field_info = params.File(field_info.default) + elif not is_scalar_field(field=field): + field.field_info = params.Body(field_info.default) - return field + return type_annotation, depends, field + + +def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool: + if is_path_param: + assert is_scalar_field( + field=param_field + ), "Path params must be of one of the supported types" + return False + elif is_scalar_field(field=param_field): + return False + elif isinstance( + param_field.field_info, (params.Query, params.Header) + ) and is_scalar_sequence_field(param_field): + return False + else: + assert isinstance( + param_field.field_info, params.Body + ), f"Param: {param_field.name} can only be a request body, using Body()" + return True def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None: @@ -416,22 +515,22 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool: return inspect.iscoroutinefunction(call) if inspect.isclass(call): return False - call = getattr(call, "__call__", None) - return inspect.iscoroutinefunction(call) + dunder_call = getattr(call, "__call__", None) # noqa: B004 + return inspect.iscoroutinefunction(dunder_call) def is_async_gen_callable(call: Callable[..., Any]) -> bool: if inspect.isasyncgenfunction(call): return True - call = getattr(call, "__call__", None) - return inspect.isasyncgenfunction(call) + dunder_call = getattr(call, "__call__", None) # noqa: B004 + return inspect.isasyncgenfunction(dunder_call) def is_gen_callable(call: Callable[..., Any]) -> bool: if inspect.isgeneratorfunction(call): return True - call = getattr(call, "__call__", None) - return inspect.isgeneratorfunction(call) + dunder_call = getattr(call, "__call__", None) # noqa: B004 + return inspect.isgeneratorfunction(dunder_call) async def solve_generator( @@ -490,7 +589,6 @@ async def solve_dependencies( name=sub_dependant.name, security_scopes=sub_dependant.security_scopes, ) - use_sub_dependant.security_scopes = sub_dependant.security_scopes solved_result = await solve_dependencies( request=request, @@ -677,7 +775,7 @@ async def request_body_to_args( fn: Callable[[], Coroutine[Any, Any, Any]] ) -> None: result = await fn() - results.append(result) + results.append(result) # noqa: B023 async with anyio.create_task_group() as tg: for sub_value in value: @@ -715,14 +813,14 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]: # in case a sub-dependency is evaluated with a single unique body field # That is combined (embedded) with other body fields for param in flat_dependant.body_params: - setattr(param.field_info, "embed", True) + setattr(param.field_info, "embed", True) # noqa: B010 model_name = "Body_" + name BodyModel: Type[BaseModel] = create_model(model_name) for f in flat_dependant.body_params: BodyModel.__fields__[f.name] = f required = any(True for f in flat_dependant.body_params if f.required) - BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None) + BodyFieldInfo_kwargs: Dict[str, Any] = {"default": None} if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params): BodyFieldInfo: Type[params.Body] = params.File elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): @@ -731,7 +829,7 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]: BodyFieldInfo = params.Body body_param_media_types = [ - getattr(f.field_info, "media_type") + f.field_info.media_type for f in flat_dependant.body_params if isinstance(f.field_info, params.Body) ] diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 4b7ffe313f..2f95bcbf66 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -54,8 +54,8 @@ def jsonable_encoder( if custom_encoder: encoder.update(custom_encoder) obj_dict = obj.dict( - include=include, # type: ignore # in Pydantic - exclude=exclude, # type: ignore # in Pydantic + include=include, + exclude=exclude, by_alias=by_alias, exclude_unset=exclude_unset, exclude_none=exclude_none, @@ -71,7 +71,18 @@ def jsonable_encoder( sqlalchemy_safe=sqlalchemy_safe, ) if dataclasses.is_dataclass(obj): - return dataclasses.asdict(obj) + obj_dict = dataclasses.asdict(obj) + return jsonable_encoder( + obj_dict, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + custom_encoder=custom_encoder, + sqlalchemy_safe=sqlalchemy_safe, + ) if isinstance(obj, Enum): return obj.value if isinstance(obj, PurePath): @@ -80,6 +91,11 @@ def jsonable_encoder( return obj if isinstance(obj, dict): encoded_dict = {} + allowed_keys = set(obj.keys()) + if include is not None: + allowed_keys &= set(include) + if exclude is not None: + allowed_keys -= set(exclude) for key, value in obj.items(): if ( ( @@ -88,7 +104,7 @@ def jsonable_encoder( or (not key.startswith("_sa")) ) and (value is not None or not exclude_none) - and ((include and key in include) or not exclude or key not in exclude) + and key in allowed_keys ): encoded_key = jsonable_encoder( key, @@ -132,18 +148,20 @@ def jsonable_encoder( if isinstance(obj, classes_tuple): return encoder(obj) - errors: List[Exception] = [] try: data = dict(obj) except Exception as e: + errors: List[Exception] = [] errors.append(e) try: data = vars(obj) except Exception as e: errors.append(e) - raise ValueError(errors) + raise ValueError(errors) from e return jsonable_encoder( data, + include=include, + exclude=exclude, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, diff --git a/fastapi/exception_handlers.py b/fastapi/exception_handlers.py index 2b286d71c7..6c2ba7fedf 100644 --- a/fastapi/exception_handlers.py +++ b/fastapi/exception_handlers.py @@ -1,19 +1,20 @@ from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import RequestValidationError +from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError +from fastapi.utils import is_body_allowed_for_status_code +from fastapi.websockets import WebSocket from starlette.exceptions import HTTPException from starlette.requests import Request -from starlette.responses import JSONResponse -from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY +from starlette.responses import JSONResponse, Response +from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, WS_1008_POLICY_VIOLATION -async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse: +async def http_exception_handler(request: Request, exc: HTTPException) -> Response: headers = getattr(exc, "headers", None) - if headers: - return JSONResponse( - {"detail": exc.detail}, status_code=exc.status_code, headers=headers - ) - else: - return JSONResponse({"detail": exc.detail}, status_code=exc.status_code) + if not is_body_allowed_for_status_code(exc.status_code): + return Response(status_code=exc.status_code, headers=headers) + return JSONResponse( + {"detail": exc.detail}, status_code=exc.status_code, headers=headers + ) async def request_validation_exception_handler( @@ -23,3 +24,11 @@ async def request_validation_exception_handler( status_code=HTTP_422_UNPROCESSABLE_ENTITY, content={"detail": jsonable_encoder(exc.errors())}, ) + + +async def websocket_request_validation_exception_handler( + websocket: WebSocket, exc: WebSocketRequestValidationError +) -> None: + await websocket.close( + code=WS_1008_POLICY_VIOLATION, reason=jsonable_encoder(exc.errors()) + ) diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 0f50acc6c5..cac5330a22 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -3,6 +3,7 @@ from typing import Any, Dict, Optional, Sequence, Type from pydantic import BaseModel, ValidationError, create_model from pydantic.error_wrappers import ErrorList from starlette.exceptions import HTTPException as StarletteHTTPException +from starlette.exceptions import WebSocketException as WebSocketException # noqa: F401 class HTTPException(StarletteHTTPException): @@ -10,7 +11,7 @@ class HTTPException(StarletteHTTPException): self, status_code: int, detail: Any = None, - headers: Optional[Dict[str, Any]] = None, + headers: Optional[Dict[str, str]] = None, ) -> None: super().__init__(status_code=status_code, detail=detail, headers=headers) diff --git a/fastapi/middleware/asyncexitstack.py b/fastapi/middleware/asyncexitstack.py index 503a68ac73..30a0ae626c 100644 --- a/fastapi/middleware/asyncexitstack.py +++ b/fastapi/middleware/asyncexitstack.py @@ -10,19 +10,16 @@ class AsyncExitStackMiddleware: self.context_name = context_name async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: - if AsyncExitStack: - dependency_exception: Optional[Exception] = None - async with AsyncExitStack() as stack: - scope[self.context_name] = stack - try: - await self.app(scope, receive, send) - except Exception as e: - dependency_exception = e - raise e - if dependency_exception: - # This exception was possibly handled by the dependency but it should - # still bubble up so that the ServerErrorMiddleware can return a 500 - # or the ExceptionMiddleware can catch and handle any other exceptions - raise dependency_exception - else: - await self.app(scope, receive, send) # pragma: no cover + dependency_exception: Optional[Exception] = None + async with AsyncExitStack() as stack: + scope[self.context_name] = stack + try: + await self.app(scope, receive, send) + except Exception as e: + dependency_exception = e + raise e + if dependency_exception: + # This exception was possibly handled by the dependency but it should + # still bubble up so that the ServerErrorMiddleware can return a 500 + # or the ExceptionMiddleware can catch and handle any other exceptions + raise dependency_exception diff --git a/fastapi/openapi/constants.py b/fastapi/openapi/constants.py index 3e69e55244..1897ad7509 100644 --- a/fastapi/openapi/constants.py +++ b/fastapi/openapi/constants.py @@ -1,3 +1,2 @@ METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"} -STATUS_CODES_WITH_NO_BODY = {100, 101, 102, 103, 204, 304} REF_PREFIX = "#/components/schemas/" diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index d6af17a850..bf335118fc 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -106,6 +106,9 @@ def get_redoc_html( + @@ -115,12 +118,14 @@ def get_redoc_html( def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse: + # copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html html = """ - + - - - + + Swagger UI: OAuth2 Redirect + + + + """ return HTMLResponse(content=html) diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 9c6598d2d1..81a24f389b 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -3,6 +3,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Union from fastapi.logger import logger from pydantic import AnyUrl, BaseModel, Field +from typing_extensions import Literal try: import email_validator # type: ignore @@ -73,7 +74,7 @@ class Server(BaseModel): class Reference(BaseModel): - ref: str = Field(..., alias="$ref") + ref: str = Field(alias="$ref") class Discriminator(BaseModel): @@ -101,28 +102,28 @@ class ExternalDocumentation(BaseModel): class Schema(BaseModel): - ref: Optional[str] = Field(None, alias="$ref") + ref: Optional[str] = Field(default=None, alias="$ref") title: Optional[str] = None multipleOf: Optional[float] = None maximum: Optional[float] = None exclusiveMaximum: Optional[float] = None minimum: Optional[float] = None exclusiveMinimum: Optional[float] = None - maxLength: Optional[int] = Field(None, gte=0) - minLength: Optional[int] = Field(None, gte=0) + maxLength: Optional[int] = Field(default=None, ge=0) + minLength: Optional[int] = Field(default=None, ge=0) pattern: Optional[str] = None - maxItems: Optional[int] = Field(None, gte=0) - minItems: Optional[int] = Field(None, gte=0) + maxItems: Optional[int] = Field(default=None, ge=0) + minItems: Optional[int] = Field(default=None, ge=0) uniqueItems: Optional[bool] = None - maxProperties: Optional[int] = Field(None, gte=0) - minProperties: Optional[int] = Field(None, gte=0) + maxProperties: Optional[int] = Field(default=None, ge=0) + minProperties: Optional[int] = Field(default=None, ge=0) required: Optional[List[str]] = None enum: Optional[List[Any]] = None type: Optional[str] = None allOf: Optional[List["Schema"]] = None oneOf: Optional[List["Schema"]] = None anyOf: Optional[List["Schema"]] = None - not_: Optional["Schema"] = Field(None, alias="not") + not_: Optional["Schema"] = Field(default=None, alias="not") items: Optional[Union["Schema", List["Schema"]]] = None properties: Optional[Dict[str, "Schema"]] = None additionalProperties: Optional[Union["Schema", Reference, bool]] = None @@ -171,7 +172,7 @@ class Encoding(BaseModel): class MediaType(BaseModel): - schema_: Optional[Union[Schema, Reference]] = Field(None, alias="schema") + schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") example: Optional[Any] = None examples: Optional[Dict[str, Union[Example, Reference]]] = None encoding: Optional[Dict[str, Encoding]] = None @@ -188,7 +189,7 @@ class ParameterBase(BaseModel): style: Optional[str] = None explode: Optional[bool] = None allowReserved: Optional[bool] = None - schema_: Optional[Union[Schema, Reference]] = Field(None, alias="schema") + schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") example: Optional[Any] = None examples: Optional[Dict[str, Union[Example, Reference]]] = None # Serialization rules for more complex scenarios @@ -200,7 +201,7 @@ class ParameterBase(BaseModel): class Parameter(ParameterBase): name: str - in_: ParameterInType = Field(..., alias="in") + in_: ParameterInType = Field(alias="in") class Header(ParameterBase): @@ -258,7 +259,7 @@ class Operation(BaseModel): class PathItem(BaseModel): - ref: Optional[str] = Field(None, alias="$ref") + ref: Optional[str] = Field(default=None, alias="$ref") summary: Optional[str] = None description: Optional[str] = None get: Optional[Operation] = None @@ -284,7 +285,7 @@ class SecuritySchemeType(Enum): class SecurityBase(BaseModel): - type_: SecuritySchemeType = Field(..., alias="type") + type_: SecuritySchemeType = Field(alias="type") description: Optional[str] = None class Config: @@ -298,18 +299,18 @@ class APIKeyIn(Enum): class APIKey(SecurityBase): - type_ = Field(SecuritySchemeType.apiKey, alias="type") - in_: APIKeyIn = Field(..., alias="in") + type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type") + in_: APIKeyIn = Field(alias="in") name: str class HTTPBase(SecurityBase): - type_ = Field(SecuritySchemeType.http, alias="type") + type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type") scheme: str class HTTPBearer(HTTPBase): - scheme = "bearer" + scheme: Literal["bearer"] = "bearer" bearerFormat: Optional[str] = None @@ -349,12 +350,14 @@ class OAuthFlows(BaseModel): class OAuth2(SecurityBase): - type_ = Field(SecuritySchemeType.oauth2, alias="type") + type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type") flows: OAuthFlows class OpenIdConnect(SecurityBase): - type_ = Field(SecuritySchemeType.openIdConnect, alias="type") + type_: SecuritySchemeType = Field( + default=SecuritySchemeType.openIdConnect, alias="type" + ) openIdConnectUrl: str diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 4eb727bd4f..6d736647b5 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -9,11 +9,7 @@ from fastapi.datastructures import DefaultPlaceholder from fastapi.dependencies.models import Dependant from fastapi.dependencies.utils import get_flat_dependant, get_flat_params from fastapi.encoders import jsonable_encoder -from fastapi.openapi.constants import ( - METHODS_WITH_BODY, - REF_PREFIX, - STATUS_CODES_WITH_NO_BODY, -) +from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX from fastapi.openapi.models import OpenAPI from fastapi.params import Body, Param from fastapi.responses import Response @@ -21,6 +17,7 @@ from fastapi.utils import ( deep_dict_update, generate_operation_id_for_path, get_model_definitions, + is_body_allowed_for_status_code, ) from pydantic import BaseModel from pydantic.fields import ModelField, Undefined @@ -184,7 +181,7 @@ def get_openapi_operation_metadata( file_name = getattr(route.endpoint, "__globals__", {}).get("__file__") if file_name: message += f" at {file_name}" - warnings.warn(message) + warnings.warn(message, stacklevel=1) operation_ids.add(operation_id) operation["operationId"] = operation_id if route.deprecated: @@ -225,9 +222,18 @@ def get_openapi_path( ) parameters.extend(operation_parameters) if parameters: - operation["parameters"] = list( - {param["name"]: param for param in parameters}.values() - ) + all_parameters = { + (param["in"], param["name"]): param for param in parameters + } + required_parameters = { + (param["in"], param["name"]): param + for param in parameters + if param.get("required") + } + # Make sure required definitions of the same parameter take precedence + # over non-required definitions + all_parameters.update(required_parameters) + operation["parameters"] = list(all_parameters.values()) if method in METHODS_WITH_BODY: request_body_oai = get_openapi_operation_request_body( body_field=route.body_field, model_name_map=model_name_map @@ -265,9 +271,8 @@ def get_openapi_path( operation.setdefault("responses", {}).setdefault(status_code, {})[ "description" ] = route.response_description - if ( - route_response_media_type - and route.status_code not in STATUS_CODES_WITH_NO_BODY + if route_response_media_type and is_body_allowed_for_status_code( + route.status_code ): response_schema = {"type": "string"} if lenient_issubclass(current_response_class, JSONResponse): @@ -327,10 +332,8 @@ def get_openapi_path( openapi_response["description"] = description http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) if (all_route_params or route.body_field) and not any( - [ - status in operation["responses"] - for status in [http422, "4XX", "default"] - ] + status in operation["responses"] + for status in [http422, "4XX", "default"] ): operation["responses"][http422] = { "description": "Validation Error", diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index a553a1461f..75f054e9dc 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -5,7 +5,7 @@ from pydantic.fields import Undefined def Path( # noqa: N802 - default: Any, + default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, @@ -44,7 +44,7 @@ def Path( # noqa: N802 def Query( # noqa: N802 - default: Any, + default: Any = Undefined, *, alias: Optional[str] = None, title: Optional[str] = None, @@ -63,7 +63,7 @@ def Query( # noqa: N802 **extra: Any, ) -> Any: return params.Query( - default, + default=default, alias=alias, title=title, description=description, @@ -83,7 +83,7 @@ def Query( # noqa: N802 def Header( # noqa: N802 - default: Any, + default: Any = Undefined, *, alias: Optional[str] = None, convert_underscores: bool = True, @@ -103,7 +103,7 @@ def Header( # noqa: N802 **extra: Any, ) -> Any: return params.Header( - default, + default=default, alias=alias, convert_underscores=convert_underscores, title=title, @@ -124,7 +124,7 @@ def Header( # noqa: N802 def Cookie( # noqa: N802 - default: Any, + default: Any = Undefined, *, alias: Optional[str] = None, title: Optional[str] = None, @@ -143,7 +143,7 @@ def Cookie( # noqa: N802 **extra: Any, ) -> Any: return params.Cookie( - default, + default=default, alias=alias, title=title, description=description, @@ -163,7 +163,7 @@ def Cookie( # noqa: N802 def Body( # noqa: N802 - default: Any, + default: Any = Undefined, *, embed: bool = False, media_type: str = "application/json", @@ -182,7 +182,7 @@ def Body( # noqa: N802 **extra: Any, ) -> Any: return params.Body( - default, + default=default, embed=embed, media_type=media_type, alias=alias, @@ -202,7 +202,7 @@ def Body( # noqa: N802 def Form( # noqa: N802 - default: Any, + default: Any = Undefined, *, media_type: str = "application/x-www-form-urlencoded", alias: Optional[str] = None, @@ -220,7 +220,7 @@ def Form( # noqa: N802 **extra: Any, ) -> Any: return params.Form( - default, + default=default, media_type=media_type, alias=alias, title=title, @@ -239,7 +239,7 @@ def Form( # noqa: N802 def File( # noqa: N802 - default: Any, + default: Any = Undefined, *, media_type: str = "multipart/form-data", alias: Optional[str] = None, @@ -257,7 +257,7 @@ def File( # noqa: N802 **extra: Any, ) -> Any: return params.File( - default, + default=default, media_type=media_type, alias=alias, title=title, diff --git a/fastapi/params.py b/fastapi/params.py index 042bbd42ff..16c5c309a7 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -16,7 +16,7 @@ class Param(FieldInfo): def __init__( self, - default: Any, + default: Any = Undefined, *, alias: Optional[str] = None, title: Optional[str] = None, @@ -39,7 +39,7 @@ class Param(FieldInfo): self.examples = examples self.include_in_schema = include_in_schema super().__init__( - default, + default=default, alias=alias, title=title, description=description, @@ -62,7 +62,7 @@ class Path(Param): def __init__( self, - default: Any, + default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, @@ -80,9 +80,10 @@ class Path(Param): include_in_schema: bool = True, **extra: Any, ): + assert default is ..., "Path parameters cannot have a default value" self.in_ = self.in_ super().__init__( - ..., + default=default, alias=alias, title=title, description=description, @@ -106,7 +107,7 @@ class Query(Param): def __init__( self, - default: Any, + default: Any = Undefined, *, alias: Optional[str] = None, title: Optional[str] = None, @@ -125,7 +126,7 @@ class Query(Param): **extra: Any, ): super().__init__( - default, + default=default, alias=alias, title=title, description=description, @@ -149,7 +150,7 @@ class Header(Param): def __init__( self, - default: Any, + default: Any = Undefined, *, alias: Optional[str] = None, convert_underscores: bool = True, @@ -170,7 +171,7 @@ class Header(Param): ): self.convert_underscores = convert_underscores super().__init__( - default, + default=default, alias=alias, title=title, description=description, @@ -194,7 +195,7 @@ class Cookie(Param): def __init__( self, - default: Any, + default: Any = Undefined, *, alias: Optional[str] = None, title: Optional[str] = None, @@ -213,7 +214,7 @@ class Cookie(Param): **extra: Any, ): super().__init__( - default, + default=default, alias=alias, title=title, description=description, @@ -235,7 +236,7 @@ class Cookie(Param): class Body(FieldInfo): def __init__( self, - default: Any, + default: Any = Undefined, *, embed: bool = False, media_type: str = "application/json", @@ -258,7 +259,7 @@ class Body(FieldInfo): self.example = example self.examples = examples super().__init__( - default, + default=default, alias=alias, title=title, description=description, @@ -279,7 +280,7 @@ class Body(FieldInfo): class Form(Body): def __init__( self, - default: Any, + default: Any = Undefined, *, media_type: str = "application/x-www-form-urlencoded", alias: Optional[str] = None, @@ -297,7 +298,7 @@ class Form(Body): **extra: Any, ): super().__init__( - default, + default=default, embed=True, media_type=media_type, alias=alias, @@ -319,7 +320,7 @@ class Form(Body): class File(Form): def __init__( self, - default: Any, + default: Any = Undefined, *, media_type: str = "multipart/form-data", alias: Optional[str] = None, @@ -337,7 +338,7 @@ class File(Form): **extra: Any, ): super().__init__( - default, + default=default, media_type=media_type, alias=alias, title=title, diff --git a/fastapi/responses.py b/fastapi/responses.py index 6cd7931571..c0a13b7555 100644 --- a/fastapi/responses.py +++ b/fastapi/responses.py @@ -27,8 +27,8 @@ class UJSONResponse(JSONResponse): class ORJSONResponse(JSONResponse): - media_type = "application/json" - def render(self, content: Any) -> bytes: assert orjson is not None, "orjson must be installed to use ORJSONResponse" - return orjson.dumps(content) + return orjson.dumps( + content, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY + ) diff --git a/fastapi/routing.py b/fastapi/routing.py index a6542c15a0..ec8af99b3a 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -3,6 +3,7 @@ import dataclasses import email.message import inspect import json +from contextlib import AsyncExitStack from enum import Enum, IntEnum from typing import ( Any, @@ -25,36 +26,42 @@ from fastapi.dependencies.utils import ( get_body_field, get_dependant, get_parameterless_sub_dependant, + get_typed_return_annotation, solve_dependencies, ) from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder -from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError -from fastapi.openapi.constants import STATUS_CODES_WITH_NO_BODY +from fastapi.exceptions import ( + FastAPIError, + RequestValidationError, + WebSocketRequestValidationError, +) from fastapi.types import DecoratedCallable from fastapi.utils import ( create_cloned_field, create_response_field, generate_unique_id, get_value_or_default, + is_body_allowed_for_status_code, ) from pydantic import BaseModel from pydantic.error_wrappers import ErrorWrapper, ValidationError from pydantic.fields import ModelField, Undefined +from pydantic.utils import lenient_issubclass from starlette import routing from starlette.concurrency import run_in_threadpool from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.responses import JSONResponse, Response -from starlette.routing import BaseRoute, Match -from starlette.routing import Mount as Mount # noqa from starlette.routing import ( + BaseRoute, + Match, compile_path, get_name, request_response, websocket_session, ) -from starlette.status import WS_1008_POLICY_VIOLATION -from starlette.types import ASGIApp, Scope +from starlette.routing import Mount as Mount # noqa +from starlette.types import ASGIApp, Lifespan, Scope from starlette.websockets import WebSocket @@ -127,7 +134,7 @@ async def serialize_response( if is_coroutine: value, errors_ = field.validate(response_content, {}, loc=("response",)) else: - value, errors_ = await run_in_threadpool( # type: ignore[misc] + value, errors_ = await run_in_threadpool( field.validate, response_content, {}, loc=("response",) ) if isinstance(errors_, ErrorWrapper): @@ -190,6 +197,9 @@ def get_request_handler( if body_field: if is_body_form: body = await request.form() + stack = request.scope.get("fastapi_astack") + assert isinstance(stack, AsyncExitStack) + stack.push_async_callback(body.close) else: body_bytes = await request.body() if body_bytes: @@ -209,7 +219,11 @@ def get_request_handler( else: body = body_bytes except json.JSONDecodeError as e: - raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc) + raise RequestValidationError( + [ErrorWrapper(e, ("body", e.pos))], body=e.doc + ) from e + except HTTPException: + raise except Exception as e: raise HTTPException( status_code=400, detail="There was an error parsing the body" @@ -232,7 +246,17 @@ def get_request_handler( if raw_response.background is None: raw_response.background = background_tasks return raw_response - response_data = await serialize_response( + response_args: Dict[str, Any] = {"background": background_tasks} + # If status_code was set, use it, otherwise use the default from the + # response class, in the case of redirect it's 307 + current_status_code = ( + status_code if status_code else sub_response.status_code + ) + if current_status_code is not None: + response_args["status_code"] = current_status_code + if sub_response.status_code: + response_args["status_code"] = sub_response.status_code + content = await serialize_response( field=response_field, response_content=raw_response, include=response_model_include, @@ -243,15 +267,10 @@ def get_request_handler( exclude_none=response_model_exclude_none, is_coroutine=is_coroutine, ) - response_args: Dict[str, Any] = {"background": background_tasks} - # If status_code was set, use it, otherwise use the default from the - # response class, in the case of redirect it's 307 - if status_code is not None: - response_args["status_code"] = status_code - response = actual_response_class(response_data, **response_args) + response = actual_response_class(content, **response_args) + if not is_body_allowed_for_status_code(response.status_code): + response.body = b"" response.headers.raw.extend(sub_response.headers.raw) - if sub_response.status_code: - response.status_code = sub_response.status_code return response return app @@ -268,7 +287,6 @@ def get_websocket_app( ) values, errors, _, _2, _3 = solved_result if errors: - await websocket.close(code=WS_1008_POLICY_VIOLATION) raise WebSocketRequestValidationError(errors) assert dependant.call is not None, "dependant.call must be a function" await dependant.call(**values) @@ -283,19 +301,27 @@ class APIWebSocketRoute(routing.WebSocketRoute): endpoint: Callable[..., Any], *, name: Optional[str] = None, + dependencies: Optional[Sequence[params.Depends]] = None, dependency_overrides_provider: Optional[Any] = None, ) -> None: self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name - self.dependant = get_dependant(path=path, call=self.endpoint) + self.dependencies = list(dependencies or []) + self.path_regex, self.path_format, self.param_convertors = compile_path(path) + self.dependant = get_dependant(path=self.path_format, call=self.endpoint) + for depends in self.dependencies[::-1]: + self.dependant.dependencies.insert( + 0, + get_parameterless_sub_dependant(depends=depends, path=self.path_format), + ) + self.app = websocket_session( get_websocket_app( dependant=self.dependant, dependency_overrides_provider=dependency_overrides_provider, ) ) - self.path_regex, self.path_format, self.param_convertors = compile_path(path) def matches(self, scope: Scope) -> Tuple[Match, Scope]: match, child_scope = super().matches(scope) @@ -310,7 +336,7 @@ class APIRoute(routing.Route): path: str, endpoint: Callable[..., Any], *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -341,6 +367,12 @@ class APIRoute(routing.Route): ) -> None: self.path = path self.endpoint = endpoint + if isinstance(response_model, DefaultPlaceholder): + return_annotation = get_typed_return_annotation(endpoint) + if lenient_issubclass(return_annotation, Response): + response_model = None + else: + response_model = return_annotation self.response_model = response_model self.summary = summary self.response_description = response_description @@ -377,8 +409,8 @@ class APIRoute(routing.Route): status_code = int(status_code) self.status_code = status_code if self.response_model: - assert ( - status_code not in STATUS_CODES_WITH_NO_BODY + assert is_body_allowed_for_status_code( + status_code ), f"Status code {status_code} must not have a response body" response_name = "Response_" + self.unique_id self.response_field = create_response_field( @@ -397,21 +429,18 @@ class APIRoute(routing.Route): else: self.response_field = None # type: ignore self.secure_cloned_response_field = None - if dependencies: - self.dependencies = list(dependencies) - else: - self.dependencies = [] + self.dependencies = list(dependencies or []) self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "") # if a "form feed" character (page break) is found in the description text, # truncate description text to the content preceding the first "form feed" - self.description = self.description.split("\f")[0] + self.description = self.description.split("\f")[0].strip() response_fields = {} for additional_status_code, response in self.responses.items(): assert isinstance(response, dict), "An additional response must be a dict" model = response.get("model") if model: - assert ( - additional_status_code not in STATUS_CODES_WITH_NO_BODY + assert is_body_allowed_for_status_code( + additional_status_code ), f"Status code {additional_status_code} must not have a response body" response_name = f"Response_{additional_status_code}_{self.unique_id}" response_field = create_response_field(name=response_name, type_=model) @@ -471,6 +500,9 @@ class APIRouter(routing.Router): route_class: Type[APIRoute] = APIRoute, on_startup: Optional[Sequence[Callable[[], Any]]] = None, on_shutdown: Optional[Sequence[Callable[[], Any]]] = None, + # the generic to Lifespan[AppType] is the type of the top level application + # which the router cannot know statically, so we use typing.Any + lifespan: Optional[Lifespan[Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, generate_unique_id_function: Callable[[APIRoute], str] = Default( @@ -483,6 +515,7 @@ class APIRouter(routing.Router): default=default, on_startup=on_startup, on_shutdown=on_shutdown, + lifespan=lifespan, ) if prefix: assert prefix.startswith("/"), "A path prefix must start with '/'" @@ -491,7 +524,7 @@ class APIRouter(routing.Router): ), "A path prefix must not end with '/', as the routes will start with '/'" self.prefix = prefix self.tags: List[Union[str, Enum]] = tags or [] - self.dependencies = list(dependencies or []) or [] + self.dependencies = list(dependencies or []) self.deprecated = deprecated self.include_in_schema = include_in_schema self.responses = responses or {} @@ -501,12 +534,31 @@ class APIRouter(routing.Router): self.default_response_class = default_response_class self.generate_unique_id_function = generate_unique_id_function + def route( + self, + path: str, + methods: Optional[List[str]] = None, + name: Optional[str] = None, + include_in_schema: bool = True, + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_route( + path, + func, + methods=methods, + name=name, + include_in_schema=include_in_schema, + ) + return func + + return decorator + def add_api_route( self, path: str, endpoint: Callable[..., Any], *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -587,7 +639,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -646,21 +698,46 @@ class APIRouter(routing.Router): return decorator def add_api_websocket_route( - self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None + self, + path: str, + endpoint: Callable[..., Any], + name: Optional[str] = None, + *, + dependencies: Optional[Sequence[params.Depends]] = None, ) -> None: + current_dependencies = self.dependencies.copy() + if dependencies: + current_dependencies.extend(dependencies) + route = APIWebSocketRoute( self.prefix + path, endpoint=endpoint, name=name, + dependencies=current_dependencies, dependency_overrides_provider=self.dependency_overrides_provider, ) self.routes.append(route) def websocket( - self, path: str, name: Optional[str] = None + self, + path: str, + name: Optional[str] = None, + *, + dependencies: Optional[Sequence[params.Depends]] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_api_websocket_route(path, func, name=name) + self.add_api_websocket_route( + path, func, name=name, dependencies=dependencies + ) + return func + + return decorator + + def websocket_route( + self, path: str, name: Union[str, None] = None + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_websocket_route(path, func, name=name) return func return decorator @@ -688,10 +765,10 @@ class APIRouter(routing.Router): ), "A path prefix must not end with '/', as the routes will start with '/'" else: for r in router.routes: - path = getattr(r, "path") + path = getattr(r, "path") # noqa: B009 name = getattr(r, "name", "unknown") if path is not None and not path: - raise Exception( + raise FastAPIError( f"Prefix and path cannot be both empty (path operation: {name})" ) if responses is None: @@ -766,8 +843,16 @@ class APIRouter(routing.Router): name=route.name, ) elif isinstance(route, APIWebSocketRoute): + current_dependencies = [] + if dependencies: + current_dependencies.extend(dependencies) + if route.dependencies: + current_dependencies.extend(route.dependencies) self.add_api_websocket_route( - prefix + route.path, route.endpoint, name=route.name + prefix + route.path, + route.endpoint, + dependencies=current_dependencies, + name=route.name, ) elif isinstance(route, routing.WebSocketRoute): self.add_websocket_route( @@ -782,7 +867,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -838,7 +923,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -894,7 +979,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -950,7 +1035,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -1006,7 +1091,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -1062,7 +1147,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -1118,7 +1203,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -1174,7 +1259,7 @@ class APIRouter(routing.Router): self, path: str, *, - response_model: Optional[Type[Any]] = None, + response_model: Any = Default(None), status_code: Optional[int] = None, tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, @@ -1199,7 +1284,6 @@ class APIRouter(routing.Router): generate_unique_id ), ) -> Callable[[DecoratedCallable], DecoratedCallable]: - return self.api_route( path=path, response_model=response_model, @@ -1226,3 +1310,12 @@ class APIRouter(routing.Router): openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, ) + + def on_event( + self, event_type: str + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_event_handler(event_type, func) + return func + + return decorator diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py index 36ab60e30f..8b2c5c0805 100644 --- a/fastapi/security/api_key.py +++ b/fastapi/security/api_key.py @@ -18,16 +18,18 @@ class APIKeyQuery(APIKeyBase): name: str, scheme_name: Optional[str] = None, description: Optional[str] = None, - auto_error: bool = True + auto_error: bool = True, ): self.model: APIKey = APIKey( - **{"in": APIKeyIn.query}, name=name, description=description + **{"in": APIKeyIn.query}, # type: ignore[arg-type] + name=name, + description=description, ) self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error async def __call__(self, request: Request) -> Optional[str]: - api_key: str = request.query_params.get(self.model.name) + api_key = request.query_params.get(self.model.name) if not api_key: if self.auto_error: raise HTTPException( @@ -45,16 +47,18 @@ class APIKeyHeader(APIKeyBase): name: str, scheme_name: Optional[str] = None, description: Optional[str] = None, - auto_error: bool = True + auto_error: bool = True, ): self.model: APIKey = APIKey( - **{"in": APIKeyIn.header}, name=name, description=description + **{"in": APIKeyIn.header}, # type: ignore[arg-type] + name=name, + description=description, ) self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error async def __call__(self, request: Request) -> Optional[str]: - api_key: str = request.headers.get(self.model.name) + api_key = request.headers.get(self.model.name) if not api_key: if self.auto_error: raise HTTPException( @@ -72,10 +76,12 @@ class APIKeyCookie(APIKeyBase): name: str, scheme_name: Optional[str] = None, description: Optional[str] = None, - auto_error: bool = True + auto_error: bool = True, ): self.model: APIKey = APIKey( - **{"in": APIKeyIn.cookie}, name=name, description=description + **{"in": APIKeyIn.cookie}, # type: ignore[arg-type] + name=name, + description=description, ) self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error diff --git a/fastapi/security/http.py b/fastapi/security/http.py index 1b473c69e7..8fc0aafd9f 100644 --- a/fastapi/security/http.py +++ b/fastapi/security/http.py @@ -38,7 +38,7 @@ class HTTPBase(SecurityBase): async def __call__( self, request: Request ) -> Optional[HTTPAuthorizationCredentials]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): if self.auto_error: @@ -67,17 +67,12 @@ class HTTPBasic(HTTPBase): async def __call__( # type: ignore self, request: Request ) -> Optional[HTTPBasicCredentials]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if self.realm: unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'} else: unauthorized_headers = {"WWW-Authenticate": "Basic"} - invalid_user_credentials_exc = HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Invalid authentication credentials", - headers=unauthorized_headers, - ) if not authorization or scheme.lower() != "basic": if self.auto_error: raise HTTPException( @@ -87,6 +82,11 @@ class HTTPBasic(HTTPBase): ) else: return None + invalid_user_credentials_exc = HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Invalid authentication credentials", + headers=unauthorized_headers, + ) try: data = b64decode(param).decode("ascii") except (ValueError, UnicodeDecodeError, binascii.Error): @@ -113,7 +113,7 @@ class HTTPBearer(HTTPBase): async def __call__( self, request: Request ) -> Optional[HTTPAuthorizationCredentials]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): if self.auto_error: @@ -148,7 +148,7 @@ class HTTPDigest(HTTPBase): async def __call__( self, request: Request ) -> Optional[HTTPAuthorizationCredentials]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): if self.auto_error: diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py index bdc6e2ea9b..938dec37cd 100644 --- a/fastapi/security/oauth2.py +++ b/fastapi/security/oauth2.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, cast from fastapi.exceptions import HTTPException from fastapi.openapi.models import OAuth2 as OAuth2Model @@ -45,12 +45,12 @@ class OAuth2PasswordRequestForm: def __init__( self, - grant_type: str = Form(None, regex="password"), - username: str = Form(...), - password: str = Form(...), - scope: str = Form(""), - client_id: Optional[str] = Form(None), - client_secret: Optional[str] = Form(None), + grant_type: str = Form(default=None, regex="password"), + username: str = Form(), + password: str = Form(), + scope: str = Form(default=""), + client_id: Optional[str] = Form(default=None), + client_secret: Optional[str] = Form(default=None), ): self.grant_type = grant_type self.username = username @@ -95,12 +95,12 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): def __init__( self, - grant_type: str = Form(..., regex="password"), - username: str = Form(...), - password: str = Form(...), - scope: str = Form(""), - client_id: Optional[str] = Form(None), - client_secret: Optional[str] = Form(None), + grant_type: str = Form(regex="password"), + username: str = Form(), + password: str = Form(), + scope: str = Form(default=""), + client_id: Optional[str] = Form(default=None), + client_secret: Optional[str] = Form(default=None), ): super().__init__( grant_type=grant_type, @@ -119,14 +119,16 @@ class OAuth2(SecurityBase): flows: Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]] = OAuthFlowsModel(), scheme_name: Optional[str] = None, description: Optional[str] = None, - auto_error: Optional[bool] = True + auto_error: bool = True, ): - self.model = OAuth2Model(flows=flows, description=description) + self.model = OAuth2Model( + flows=cast(OAuthFlowsModel, flows), description=description + ) self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error async def __call__(self, request: Request) -> Optional[str]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") if not authorization: if self.auto_error: raise HTTPException( @@ -148,7 +150,9 @@ class OAuth2PasswordBearer(OAuth2): ): if not scopes: scopes = {} - flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) + flows = OAuthFlowsModel( + password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes}) + ) super().__init__( flows=flows, scheme_name=scheme_name, @@ -157,7 +161,7 @@ class OAuth2PasswordBearer(OAuth2): ) async def __call__(self, request: Request) -> Optional[str]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": if self.auto_error: @@ -185,12 +189,15 @@ class OAuth2AuthorizationCodeBearer(OAuth2): if not scopes: scopes = {} flows = OAuthFlowsModel( - authorizationCode={ - "authorizationUrl": authorizationUrl, - "tokenUrl": tokenUrl, - "refreshUrl": refreshUrl, - "scopes": scopes, - } + authorizationCode=cast( + Any, + { + "authorizationUrl": authorizationUrl, + "tokenUrl": tokenUrl, + "refreshUrl": refreshUrl, + "scopes": scopes, + }, + ) ) super().__init__( flows=flows, @@ -200,7 +207,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ) async def __call__(self, request: Request) -> Optional[str]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": if self.auto_error: diff --git a/fastapi/security/open_id_connect_url.py b/fastapi/security/open_id_connect_url.py index dfe9f7b255..4e65f1f6c4 100644 --- a/fastapi/security/open_id_connect_url.py +++ b/fastapi/security/open_id_connect_url.py @@ -14,7 +14,7 @@ class OpenIdConnect(SecurityBase): openIdConnectUrl: str, scheme_name: Optional[str] = None, description: Optional[str] = None, - auto_error: bool = True + auto_error: bool = True, ): self.model = OpenIdConnectModel( openIdConnectUrl=openIdConnectUrl, description=description @@ -23,7 +23,7 @@ class OpenIdConnect(SecurityBase): self.auto_error = auto_error async def __call__(self, request: Request) -> Optional[str]: - authorization: str = request.headers.get("Authorization") + authorization = request.headers.get("Authorization") if not authorization: if self.auto_error: raise HTTPException( diff --git a/fastapi/security/utils.py b/fastapi/security/utils.py index 2da0dd20f3..fa7a450b74 100644 --- a/fastapi/security/utils.py +++ b/fastapi/security/utils.py @@ -1,7 +1,9 @@ -from typing import Tuple +from typing import Optional, Tuple -def get_authorization_scheme_param(authorization_header_value: str) -> Tuple[str, str]: +def get_authorization_scheme_param( + authorization_header_value: Optional[str], +) -> Tuple[str, str]: if not authorization_header_value: return "", "" scheme, _, param = authorization_header_value.partition(" ") diff --git a/fastapi/utils.py b/fastapi/utils.py index 9d720feb37..9b9ebcb852 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -1,9 +1,19 @@ -import functools import re import warnings from dataclasses import is_dataclass from enum import Enum -from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Type, Union, cast +from typing import ( + TYPE_CHECKING, + Any, + Dict, + MutableMapping, + Optional, + Set, + Type, + Union, + cast, +) +from weakref import WeakKeyDictionary import fastapi from fastapi.datastructures import DefaultPlaceholder, DefaultType @@ -17,6 +27,28 @@ from pydantic.utils import lenient_issubclass if TYPE_CHECKING: # pragma: nocover from .routing import APIRoute +# Cache for `create_cloned_field` +_CLONED_TYPES_CACHE: MutableMapping[ + Type[BaseModel], Type[BaseModel] +] = WeakKeyDictionary() + + +def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool: + if status_code is None: + return True + # Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1 + if status_code in { + "default", + "1XX", + "2XX", + "3XX", + "4XX", + "5XX", + }: + return True + current_status_code = int(status_code) + return not (current_status_code < 200 or current_status_code in {204, 304}) + def get_model_definitions( *, @@ -30,6 +62,8 @@ def get_model_definitions( ) definitions.update(m_definitions) model_name = model_name_map[model] + if "description" in m_schema: + m_schema["description"] = m_schema["description"].split("\f")[0] definitions[model_name] = m_schema return definitions @@ -43,7 +77,7 @@ def create_response_field( type_: Type[Any], class_validators: Optional[Dict[str, Validator]] = None, default: Optional[Any] = None, - required: Union[bool, UndefinedType] = False, + required: Union[bool, UndefinedType] = True, model_config: Type[BaseConfig] = BaseConfig, field_info: Optional[FieldInfo] = None, alias: Optional[str] = None, @@ -52,35 +86,41 @@ def create_response_field( Create a new response field. Raises if type_ is invalid. """ class_validators = class_validators or {} - field_info = field_info or FieldInfo(None) - - response_field = functools.partial( - ModelField, - name=name, - type_=type_, - class_validators=class_validators, - default=default, - required=required, - model_config=model_config, - alias=alias, - ) + field_info = field_info or FieldInfo() try: - return response_field(field_info=field_info) + return ModelField( + name=name, + type_=type_, + class_validators=class_validators, + default=default, + required=required, + model_config=model_config, + alias=alias, + field_info=field_info, + ) except RuntimeError: raise fastapi.exceptions.FastAPIError( - f"Invalid args for response field! Hint: check that {type_} is a valid pydantic field type" - ) + "Invalid args for response field! Hint: " + f"check that {type_} is a valid Pydantic field type. " + "If you are using a return type annotation that is not a valid Pydantic " + "field (e.g. Union[Response, dict, None]) you can disable generating the " + "response model from the type annotation with the path operation decorator " + "parameter response_model=None. Read more: " + "https://fastapi.tiangolo.com/tutorial/response-model/" + ) from None def create_cloned_field( field: ModelField, *, - cloned_types: Optional[Dict[Type[BaseModel], Type[BaseModel]]] = None, + cloned_types: Optional[MutableMapping[Type[BaseModel], Type[BaseModel]]] = None, ) -> ModelField: - # _cloned_types has already cloned types, to support recursive models + # cloned_types caches already cloned types to support recursive models and improve + # performance by avoiding unecessary cloning if cloned_types is None: - cloned_types = dict() + cloned_types = _CLONED_TYPES_CACHE + original_type = field.type_ if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"): original_type = original_type.__pydantic_model__ @@ -133,14 +173,14 @@ def generate_operation_id_for_path( stacklevel=2, ) operation_id = name + path - operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id) + operation_id = re.sub(r"\W", "_", operation_id) operation_id = operation_id + "_" + method.lower() return operation_id def generate_unique_id(route: "APIRoute") -> str: operation_id = route.name + route.path_format - operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id) + operation_id = re.sub(r"\W", "_", operation_id) assert route.methods operation_id = operation_id + "_" + list(route.methods)[0].lower() return operation_id @@ -154,6 +194,12 @@ def deep_dict_update(main_dict: Dict[Any, Any], update_dict: Dict[Any, Any]) -> and isinstance(value, dict) ): deep_dict_update(main_dict[key], value) + elif ( + key in main_dict + and isinstance(main_dict[key], list) + and isinstance(update_dict[key], list) + ): + main_dict[key] = main_dict[key] + update_dict[key] else: main_dict[key] = value diff --git a/fastapi/websockets.py b/fastapi/websockets.py index bed672acff..55a4ac4a1a 100644 --- a/fastapi/websockets.py +++ b/fastapi/websockets.py @@ -1,2 +1,3 @@ from starlette.websockets import WebSocket as WebSocket # noqa from starlette.websockets import WebSocketDisconnect as WebSocketDisconnect # noqa +from starlette.websockets import WebSocketState as WebSocketState # noqa diff --git a/pyproject.toml b/pyproject.toml index 5ffdf93ad3..5c0d3c48ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,16 @@ [build-system] -requires = ["flit"] -build-backend = "flit.buildapi" +requires = ["hatchling >= 1.13.0"] +build-backend = "hatchling.build" -[tool.flit.metadata] -module = "fastapi" -author = "Sebastián Ramírez" -author-email = "tiangolo@gmail.com" -home-page = "https://github.com/tiangolo/fastapi" +[project] +name = "fastapi" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +readme = "README.md" +requires-python = ">=3.7" +license = "MIT" +authors = [ + { name = "Sebastián Ramírez", email = "tiangolo@gmail.com" }, +] classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", @@ -23,102 +27,48 @@ classifiers = [ "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: FastAPI", + "Framework :: Pydantic", + "Framework :: Pydantic :: 1", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP", ] -requires = [ - "starlette==0.19.1", - "pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0", +dependencies = [ + "starlette>=0.27.0,<0.28.0", + "pydantic>=1.7.4,!=1.8,!=1.8.1,<2.0.0", ] -description-file = "README.md" -requires-python = ">=3.6.1" +dynamic = ["version"] -[tool.flit.metadata.urls] +[project.urls] +Homepage = "https://github.com/tiangolo/fastapi" Documentation = "https://fastapi.tiangolo.com/" +Repository = "https://github.com/tiangolo/fastapi" -[tool.flit.metadata.requires-extra] -test = [ - "pytest >=6.2.4,<7.0.0", - "pytest-cov >=2.12.0,<4.0.0", - "mypy ==0.910", - "flake8 >=3.8.3,<4.0.0", - "black == 22.3.0", - "isort >=5.0.6,<6.0.0", - "requests >=2.24.0,<3.0.0", - "httpx >=0.14.0,<0.19.0", - "email_validator >=1.1.1,<2.0.0", - "sqlalchemy >=1.3.18,<1.5.0", - "peewee >=3.13.3,<4.0.0", - "databases[sqlite] >=0.3.2,<0.6.0", - "orjson >=3.2.1,<4.0.0", - "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0", - "python-multipart >=0.0.5,<0.0.6", - "flask >=1.1.2,<3.0.0", - "anyio[trio] >=3.2.1,<4.0.0", - - # types - "types-ujson ==4.2.1", - "types-orjson ==3.6.2", - "types-dataclasses ==0.6.5; python_version<'3.7'", -] -doc = [ - "mkdocs >=1.1.2,<2.0.0", - "mkdocs-material >=8.1.4,<9.0.0", - "mdx-include >=1.4.1,<2.0.0", - "mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0", - # TODO: upgrade and enable typer-cli once it supports Click 8.x.x - # "typer-cli >=0.0.12,<0.0.13", - "typer >=0.4.1,<0.5.0", - "pyyaml >=5.3.1,<7.0.0", -] -dev = [ - "python-jose[cryptography] >=3.3.0,<4.0.0", - "passlib[bcrypt] >=1.7.2,<2.0.0", - "autoflake >=1.4.0,<2.0.0", - "flake8 >=3.8.3,<4.0.0", - "uvicorn[standard] >=0.12.0,<0.18.0", - "pre-commit >=2.17.0,<3.0.0", -] +[project.optional-dependencies] all = [ - "requests >=2.24.0,<3.0.0", - "jinja2 >=2.11.2,<4.0.0", - "python-multipart >=0.0.5,<0.0.6", - "itsdangerous >=1.1.0,<3.0.0", - "pyyaml >=5.3.1,<7.0.0", - "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0", - "orjson >=3.2.1,<4.0.0", - "email_validator >=1.1.1,<2.0.0", - "uvicorn[standard] >=0.12.0,<0.18.0", + "httpx >=0.23.0", + "jinja2 >=2.11.2", + "python-multipart >=0.0.5", + "itsdangerous >=1.1.0", + "pyyaml >=5.3.1", + "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0", + "orjson >=3.2.1", + "email_validator >=1.1.1", + "uvicorn[standard] >=0.12.0", ] -[tool.isort] -profile = "black" -known_third_party = ["fastapi", "pydantic", "starlette"] +[tool.hatch.version] +path = "fastapi/__init__.py" [tool.mypy] -# --strict -disallow_any_generics = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_defs = true -disallow_incomplete_defs = true -check_untyped_defs = true -disallow_untyped_decorators = true -no_implicit_optional = true -warn_redundant_casts = true -warn_unused_ignores = true -warn_return_any = true -implicit_reexport = false -strict_equality = true -# --strict end +strict = true [[tool.mypy.overrides]] module = "fastapi.concurrency" @@ -142,6 +92,62 @@ filterwarnings = [ # TODO: needed by asyncio in Python 3.9.7 https://bugs.python.org/issue45097, try to remove on 3.9.8 'ignore:The loop argument is deprecated since Python 3\.8, and scheduled for removal in Python 3\.10:DeprecationWarning:asyncio', 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette', - # TODO: remove after dropping support for Python 3.6 - 'ignore:Python 3.6 is no longer supported by the Python core team. Therefore, support for it is deprecated in cryptography and will be removed in a future release.:UserWarning:jose', + # TODO: remove after upgrading HTTPX to a version newer than 0.23.0 + # Including PR: https://github.com/encode/httpx/pull/2309 + "ignore:'cgi' is deprecated:DeprecationWarning", + # For passlib + "ignore:'crypt' is deprecated and slated for removal in Python 3.13:DeprecationWarning", + # see https://trio.readthedocs.io/en/stable/history.html#trio-0-22-0-2022-09-28 + "ignore:You seem to already have a custom.*:RuntimeWarning:trio", + "ignore::trio.TrioDeprecationWarning", + # TODO remove pytest-cov + 'ignore::pytest.PytestDeprecationWarning:pytest_cov', ] + +[tool.coverage.run] +parallel = true +source = [ + "docs_src", + "tests", + "fastapi" +] +context = '${CONTEXT}' +omit = [ + "docs_src/response_model/tutorial003_04.py", + "docs_src/response_model/tutorial003_04_py310.py", +] + +[tool.ruff] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "C901", # too complex +] + +[tool.ruff.per-file-ignores] +"__init__.py" = ["F401"] +"docs_src/dependencies/tutorial007.py" = ["F821"] +"docs_src/dependencies/tutorial008.py" = ["F821"] +"docs_src/dependencies/tutorial009.py" = ["F821"] +"docs_src/dependencies/tutorial010.py" = ["F821"] +"docs_src/custom_response/tutorial007.py" = ["B007"] +"docs_src/dataclasses/tutorial003.py" = ["I001"] +"docs_src/path_operation_advanced_configuration/tutorial007.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002.py" = ["B904"] +"docs_src/dependencies/tutorial008_an.py" = ["F821"] +"docs_src/dependencies/tutorial008_an_py39.py" = ["F821"] +"docs_src/query_params_str_validations/tutorial012_an.py" = ["B006"] +"docs_src/query_params_str_validations/tutorial012_an_py39.py" = ["B006"] +"docs_src/query_params_str_validations/tutorial013_an.py" = ["B006"] +"docs_src/query_params_str_validations/tutorial013_an_py39.py" = ["B006"] + +[tool.ruff.isort] +known-third-party = ["fastapi", "pydantic", "starlette"] diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000000..2c5f71ec04 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,14 @@ +-e . +mkdocs==1.4.3 +mkdocs-material==9.1.16 +mdx-include >=1.4.1,<2.0.0 +mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 +typer-cli >=0.0.13,<0.0.14 +typer[all] >=0.6.1,<0.8.0 +pyyaml >=5.3.1,<7.0.0 +# For Material for MkDocs, Chinese search +jieba==0.42.1 +# For image processing by Material for MkDocs +pillow==9.5.0 +# For image processing by Material for MkDocs +cairosvg==2.7.0 diff --git a/requirements-tests.txt b/requirements-tests.txt new file mode 100644 index 0000000000..5cedde84d4 --- /dev/null +++ b/requirements-tests.txt @@ -0,0 +1,25 @@ +-e . +pytest >=7.1.3,<8.0.0 +coverage[toml] >= 6.5.0,< 8.0 +mypy ==1.4.0 +ruff ==0.0.275 +black == 23.3.0 +httpx >=0.23.0,<0.24.0 +email_validator >=1.1.1,<2.0.0 +# TODO: once removing databases from tutorial, upgrade SQLAlchemy +# probably when including SQLModel +sqlalchemy >=1.3.18,<1.4.43 +peewee >=3.13.3,<4.0.0 +databases[sqlite] >=0.3.2,<0.7.0 +orjson >=3.2.1,<4.0.0 +ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0 +python-multipart >=0.0.5,<0.0.7 +flask >=1.1.2,<3.0.0 +anyio[trio] >=3.2.1,<4.0.0 +python-jose[cryptography] >=3.3.0,<4.0.0 +pyyaml >=5.3.1,<7.0.0 +passlib[bcrypt] >=1.7.2,<2.0.0 + +# types +types-ujson ==5.7.0.1 +types-orjson ==3.6.2 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..7e746016a4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +-e .[all] +-r requirements-tests.txt +-r requirements-docs.txt +uvicorn[standard] >=0.12.0,<0.23.0 +pre-commit >=2.17.0,<4.0.0 diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh index 383ad3f446..ebf864afa3 100755 --- a/scripts/build-docs.sh +++ b/scripts/build-docs.sh @@ -3,4 +3,6 @@ set -e set -x +# Check README.md is up to date +python ./scripts/docs.py verify-readme python ./scripts/docs.py build-all diff --git a/scripts/docs.py b/scripts/docs.py index 40569e193d..20838be6a7 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -1,10 +1,15 @@ +import json +import logging import os import re import shutil +import subprocess +from functools import lru_cache from http.server import HTTPServer, SimpleHTTPRequestHandler +from importlib import metadata from multiprocessing import Pool from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Union import mkdocs.commands.build import mkdocs.commands.serve @@ -14,6 +19,8 @@ import typer import yaml from jinja2 import Template +logging.basicConfig(level=logging.INFO) + app = typer.Typer() mkdocs_name = "mkdocs.yml" @@ -25,19 +32,27 @@ missing_translation_snippet = """ docs_path = Path("docs") en_docs_path = Path("docs/en") en_config_path: Path = en_docs_path / mkdocs_name +site_path = Path("site").absolute() +build_site_path = Path("site_build").absolute() -def get_en_config() -> dict: +@lru_cache() +def is_mkdocs_insiders() -> bool: + version = metadata.version("mkdocs-material") + return "insiders" in version + + +def get_en_config() -> Dict[str, Any]: return mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8")) -def get_lang_paths(): +def get_lang_paths() -> List[Path]: return sorted(docs_path.iterdir()) -def lang_callback(lang: Optional[str]): +def lang_callback(lang: Optional[str]) -> Union[str, None]: if lang is None: - return + return None if not lang.isalpha() or len(lang) != 2: typer.echo("Use a 2 letter language code, like: es") raise typer.Abort() @@ -52,33 +67,12 @@ def complete_existing_lang(incomplete: str): yield lang_path.name -def get_base_lang_config(lang: str): - en_config = get_en_config() - fastapi_url_base = "https://fastapi.tiangolo.com/" - new_config = en_config.copy() - new_config["site_url"] = en_config["site_url"] + f"{lang}/" - new_config["theme"]["logo"] = fastapi_url_base + en_config["theme"]["logo"] - new_config["theme"]["favicon"] = fastapi_url_base + en_config["theme"]["favicon"] - new_config["theme"]["language"] = lang - new_config["nav"] = en_config["nav"][:2] - extra_css = [] - css: str - for css in en_config["extra_css"]: - if css.startswith("http"): - extra_css.append(css) - else: - extra_css.append(fastapi_url_base + css) - new_config["extra_css"] = extra_css - - extra_js = [] - js: str - for js in en_config["extra_javascript"]: - if js.startswith("http"): - extra_js.append(js) - else: - extra_js.append(fastapi_url_base + js) - new_config["extra_javascript"] = extra_js - return new_config +@app.callback() +def callback() -> None: + if is_mkdocs_insiders(): + os.environ["INSIDERS_FILE"] = "../en/mkdocs.insiders.yml" + # For MacOS with insiders and Cairo + os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = "/opt/homebrew/lib" @app.command() @@ -93,12 +87,8 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)): typer.echo(f"The language was already created: {lang}") raise typer.Abort() new_path.mkdir() - new_config = get_base_lang_config(lang) new_config_path: Path = Path(new_path) / mkdocs_name - new_config_path.write_text( - yaml.dump(new_config, sort_keys=False, width=200, allow_unicode=True), - encoding="utf-8", - ) + new_config_path.write_text("INHERIT: ../en/mkdocs.yml\n", encoding="utf-8") new_config_docs_path: Path = new_path / "docs" new_config_docs_path.mkdir() en_index_path: Path = en_docs_path / "docs" / "index.md" @@ -106,11 +96,8 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)): en_index_content = en_index_path.read_text(encoding="utf-8") new_index_content = f"{missing_translation_snippet}\n\n{en_index_content}" new_index_path.write_text(new_index_content, encoding="utf-8") - new_overrides_gitignore_path = new_path / "overrides" / ".gitignore" - new_overrides_gitignore_path.parent.mkdir(parents=True, exist_ok=True) - new_overrides_gitignore_path.write_text("") typer.secho(f"Successfully initialized: {new_path}", color=typer.colors.GREEN) - update_languages(lang=None) + update_languages() @app.command() @@ -118,89 +105,35 @@ def build_lang( lang: str = typer.Argument( ..., callback=lang_callback, autocompletion=complete_existing_lang ) -): +) -> None: """ - Build the docs for a language, filling missing pages with translation notifications. + Build the docs for a language. """ + insiders_env_file = os.environ.get("INSIDERS_FILE") + print(f"Insiders file {insiders_env_file}") + if is_mkdocs_insiders(): + print("Using insiders") lang_path: Path = Path("docs") / lang if not lang_path.is_dir(): typer.echo(f"The language translation doesn't seem to exist yet: {lang}") raise typer.Abort() typer.echo(f"Building docs for: {lang}") - build_dir_path = Path("docs_build") - build_dir_path.mkdir(exist_ok=True) - build_lang_path = build_dir_path / lang - en_lang_path = Path("docs/en") - site_path = Path("site").absolute() + build_site_dist_path = build_site_path / lang if lang == "en": dist_path = site_path + # Don't remove en dist_path as it might already contain other languages. + # When running build_all(), that function already removes site_path. + # All this is only relevant locally, on GitHub Actions all this is done through + # artifacts and multiple workflows, so it doesn't matter if directories are + # removed or not. else: - dist_path: Path = site_path / lang - shutil.rmtree(build_lang_path, ignore_errors=True) - shutil.copytree(lang_path, build_lang_path) - shutil.copytree(en_docs_path / "data", build_lang_path / "data") - overrides_src = en_docs_path / "overrides" - overrides_dest = build_lang_path / "overrides" - for path in overrides_src.iterdir(): - dest_path = overrides_dest / path.name - if not dest_path.exists(): - shutil.copy(path, dest_path) - en_config_path: Path = en_lang_path / mkdocs_name - en_config: dict = mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8")) - nav = en_config["nav"] - lang_config_path: Path = lang_path / mkdocs_name - lang_config: dict = mkdocs.utils.yaml_load( - lang_config_path.read_text(encoding="utf-8") - ) - lang_nav = lang_config["nav"] - # Exclude first 2 entries FastAPI and Languages, for custom handling - use_nav = nav[2:] - lang_use_nav = lang_nav[2:] - file_to_nav = get_file_to_nav_map(use_nav) - sections = get_sections(use_nav) - lang_file_to_nav = get_file_to_nav_map(lang_use_nav) - use_lang_file_to_nav = get_file_to_nav_map(lang_use_nav) - for file in file_to_nav: - file_path = Path(file) - lang_file_path: Path = build_lang_path / "docs" / file_path - en_file_path: Path = en_lang_path / "docs" / file_path - lang_file_path.parent.mkdir(parents=True, exist_ok=True) - if not lang_file_path.is_file(): - en_text = en_file_path.read_text(encoding="utf-8") - lang_text = get_text_with_translate_missing(en_text) - lang_file_path.write_text(lang_text, encoding="utf-8") - file_key = file_to_nav[file] - use_lang_file_to_nav[file] = file_key - if file_key: - composite_key = () - new_key = () - for key_part in file_key: - composite_key += (key_part,) - key_first_file = sections[composite_key] - if key_first_file in lang_file_to_nav: - new_key = lang_file_to_nav[key_first_file] - else: - new_key += (key_part,) - use_lang_file_to_nav[file] = new_key - key_to_section = {(): []} - for file, orig_file_key in file_to_nav.items(): - if file in use_lang_file_to_nav: - file_key = use_lang_file_to_nav[file] - else: - file_key = orig_file_key - section = get_key_section(key_to_section=key_to_section, key=file_key) - section.append(file) - new_nav = key_to_section[()] - export_lang_nav = [lang_nav[0], nav[1]] + new_nav - lang_config["nav"] = export_lang_nav - build_lang_config_path: Path = build_lang_path / mkdocs_name - build_lang_config_path.write_text( - yaml.dump(lang_config, sort_keys=False, width=200, allow_unicode=True), - encoding="utf-8", - ) + dist_path = site_path / lang + shutil.rmtree(dist_path, ignore_errors=True) current_dir = os.getcwd() - os.chdir(build_lang_path) - mkdocs.commands.build.build(mkdocs.config.load_config(site_dir=str(dist_path))) + os.chdir(lang_path) + shutil.rmtree(build_site_dist_path, ignore_errors=True) + subprocess.run(["mkdocs", "build", "--site-dir", build_site_dist_path], check=True) + shutil.copytree(build_site_dist_path, dist_path, dirs_exist_ok=True) os.chdir(current_dir) typer.secho(f"Successfully built docs for: {lang}", color=typer.colors.GREEN) @@ -217,7 +150,7 @@ index_sponsors_template = """ """ -def generate_readme_content(): +def generate_readme_content() -> str: en_index = en_docs_path / "docs" / "index.md" content = en_index.read_text("utf-8") match_start = re.search(r"", content) @@ -237,7 +170,7 @@ def generate_readme_content(): @app.command() -def generate_readme(): +def generate_readme() -> None: """ Generate README.md content from main index.md """ @@ -248,7 +181,7 @@ def generate_readme(): @app.command() -def verify_readme(): +def verify_readme() -> None: """ Verify README.md content from main index.md """ @@ -265,56 +198,31 @@ def verify_readme(): @app.command() -def build_all(): +def build_all() -> None: """ Build mkdocs site for en, and then build each language inside, end result is located at directory ./site/ with each language inside. """ - site_path = Path("site").absolute() - update_languages(lang=None) - current_dir = os.getcwd() - os.chdir(en_docs_path) - typer.echo("Building docs for: en") - mkdocs.commands.build.build(mkdocs.config.load_config(site_dir=str(site_path))) - os.chdir(current_dir) - langs = [] - for lang in get_lang_paths(): - if lang == en_docs_path or not lang.is_dir(): - continue - langs.append(lang.name) + update_languages() + shutil.rmtree(site_path, ignore_errors=True) + langs = [lang.name for lang in get_lang_paths() if lang.is_dir()] cpu_count = os.cpu_count() or 1 - with Pool(cpu_count * 2) as p: + process_pool_size = cpu_count * 4 + typer.echo(f"Using process pool size: {process_pool_size}") + with Pool(process_pool_size) as p: p.map(build_lang, langs) -def update_single_lang(lang: str): - lang_path = docs_path / lang - typer.echo(f"Updating {lang_path.name}") - update_config(lang_path.name) - - @app.command() -def update_languages( - lang: str = typer.Argument( - None, callback=lang_callback, autocompletion=complete_existing_lang - ) -): +def update_languages() -> None: """ Update the mkdocs.yml file Languages section including all the available languages. - - The LANG argument is a 2-letter language code. If it's not provided, update all the - mkdocs.yml files (for all the languages). """ - if lang is None: - for lang_path in get_lang_paths(): - if lang_path.is_dir(): - update_single_lang(lang_path.name) - else: - update_single_lang(lang) + update_config() @app.command() -def serve(): +def serve() -> None: """ A quick server to preview a built site with translations. @@ -331,7 +239,7 @@ def serve(): os.chdir("site") server_address = ("", 8008) server = HTTPServer(server_address, SimpleHTTPRequestHandler) - typer.echo(f"Serving at: http://127.0.0.1:8008") + typer.echo("Serving at: http://127.0.0.1:8008") server.serve_forever() @@ -340,7 +248,7 @@ def live( lang: str = typer.Argument( None, callback=lang_callback, autocompletion=complete_existing_lang ) -): +) -> None: """ Serve with livereload a docs site for a specific language. @@ -357,18 +265,8 @@ def live( mkdocs.commands.serve.serve(dev_addr="127.0.0.1:8008") -def update_config(lang: str): - lang_path: Path = docs_path / lang - config_path = lang_path / mkdocs_name - current_config: dict = mkdocs.utils.yaml_load( - config_path.read_text(encoding="utf-8") - ) - if lang == "en": - config = get_en_config() - else: - config = get_base_lang_config(lang) - config["nav"] = current_config["nav"] - config["theme"]["language"] = current_config["theme"]["language"] +def update_config() -> None: + config = get_en_config() languages = [{"en": "/"}] alternate: List[Dict[str, str]] = config["extra"].get("alternate", []) alternate_dict = {alt["link"]: alt["name"] for alt in alternate} @@ -388,61 +286,19 @@ def update_config(lang: str): new_alternate.append({"link": url, "name": use_name}) config["nav"][1] = {"Languages": languages} config["extra"]["alternate"] = new_alternate - config_path.write_text( + en_config_path.write_text( yaml.dump(config, sort_keys=False, width=200, allow_unicode=True), encoding="utf-8", ) -def get_key_section( - *, key_to_section: Dict[Tuple[str, ...], list], key: Tuple[str, ...] -) -> list: - if key in key_to_section: - return key_to_section[key] - super_key = key[:-1] - title = key[-1] - super_section = get_key_section(key_to_section=key_to_section, key=super_key) - new_section = [] - super_section.append({title: new_section}) - key_to_section[key] = new_section - return new_section - - -def get_text_with_translate_missing(text: str) -> str: - lines = text.splitlines() - lines.insert(1, missing_translation_snippet) - new_text = "\n".join(lines) - return new_text - - -def get_file_to_nav_map(nav: list) -> Dict[str, Tuple[str, ...]]: - file_to_nav = {} - for item in nav: - if type(item) is str: - file_to_nav[item] = tuple() - elif type(item) is dict: - item_key = list(item.keys())[0] - sub_nav = item[item_key] - sub_file_to_nav = get_file_to_nav_map(sub_nav) - for k, v in sub_file_to_nav.items(): - file_to_nav[k] = (item_key,) + v - return file_to_nav - - -def get_sections(nav: list) -> Dict[Tuple[str, ...], str]: - sections = {} - for item in nav: - if type(item) is str: - continue - elif type(item) is dict: - item_key = list(item.keys())[0] - sub_nav = item[item_key] - sections[(item_key,)] = sub_nav[0] - sub_sections = get_sections(sub_nav) - for k, v in sub_sections.items(): - new_key = (item_key,) + k - sections[new_key] = v - return sections +@app.command() +def langs_json(): + langs = [] + for lang_path in get_lang_paths(): + if lang_path.is_dir(): + langs.append(lang_path.name) + print(json.dumps(langs)) if __name__ == "__main__": diff --git a/scripts/format.sh b/scripts/format.sh index ee4fbf1a5b..3fb3eb4f19 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,6 +1,5 @@ #!/bin/sh -e set -x -autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place docs_src fastapi tests scripts --exclude=__init__.py +ruff fastapi tests docs_src scripts --fix black fastapi tests docs_src scripts -isort fastapi tests docs_src scripts diff --git a/scripts/lint.sh b/scripts/lint.sh index 2e2072cf18..4db5caa962 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -4,6 +4,5 @@ set -e set -x mypy fastapi -flake8 fastapi tests +ruff fastapi tests docs_src scripts black fastapi tests --check -isort fastapi tests docs_src scripts --check-only diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py new file mode 100644 index 0000000000..008751f8ac --- /dev/null +++ b/scripts/mkdocs_hooks.py @@ -0,0 +1,132 @@ +from functools import lru_cache +from pathlib import Path +from typing import Any, List, Union + +import material +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.structure.files import File, Files +from mkdocs.structure.nav import Link, Navigation, Section +from mkdocs.structure.pages import Page + + +@lru_cache() +def get_missing_translation_content(docs_dir: str) -> str: + docs_dir_path = Path(docs_dir) + missing_translation_path = docs_dir_path.parent.parent / "missing-translation.md" + return missing_translation_path.read_text(encoding="utf-8") + + +@lru_cache() +def get_mkdocs_material_langs() -> List[str]: + material_path = Path(material.__file__).parent + material_langs_path = material_path / "partials" / "languages" + langs = [file.stem for file in material_langs_path.glob("*.html")] + return langs + + +class EnFile(File): + pass + + +def on_config(config: MkDocsConfig, **kwargs: Any) -> MkDocsConfig: + available_langs = get_mkdocs_material_langs() + dir_path = Path(config.docs_dir) + lang = dir_path.parent.name + if lang in available_langs: + config.theme["language"] = lang + if not (config.site_url or "").endswith(f"{lang}/") and not lang == "en": + config.site_url = f"{config.site_url}{lang}/" + return config + + +def resolve_file(*, item: str, files: Files, config: MkDocsConfig) -> None: + item_path = Path(config.docs_dir) / item + if not item_path.is_file(): + en_src_dir = (Path(config.docs_dir) / "../../en/docs").resolve() + potential_path = en_src_dir / item + if potential_path.is_file(): + files.append( + EnFile( + path=item, + src_dir=str(en_src_dir), + dest_dir=config.site_dir, + use_directory_urls=config.use_directory_urls, + ) + ) + + +def resolve_files(*, items: List[Any], files: Files, config: MkDocsConfig) -> None: + for item in items: + if isinstance(item, str): + resolve_file(item=item, files=files, config=config) + elif isinstance(item, dict): + assert len(item) == 1 + values = list(item.values()) + if not values: + continue + if isinstance(values[0], str): + resolve_file(item=values[0], files=files, config=config) + elif isinstance(values[0], list): + resolve_files(items=values[0], files=files, config=config) + else: + raise ValueError(f"Unexpected value: {values}") + + +def on_files(files: Files, *, config: MkDocsConfig) -> Files: + resolve_files(items=config.nav or [], files=files, config=config) + if "logo" in config.theme: + resolve_file(item=config.theme["logo"], files=files, config=config) + if "favicon" in config.theme: + resolve_file(item=config.theme["favicon"], files=files, config=config) + resolve_files(items=config.extra_css, files=files, config=config) + resolve_files(items=config.extra_javascript, files=files, config=config) + return files + + +def generate_renamed_section_items( + items: List[Union[Page, Section, Link]], *, config: MkDocsConfig +) -> List[Union[Page, Section, Link]]: + new_items: List[Union[Page, Section, Link]] = [] + for item in items: + if isinstance(item, Section): + new_title = item.title + new_children = generate_renamed_section_items(item.children, config=config) + first_child = new_children[0] + if isinstance(first_child, Page): + if first_child.file.src_path.endswith("index.md"): + # Read the source so that the title is parsed and available + first_child.read_source(config=config) + new_title = first_child.title or new_title + # Creating a new section makes it render it collapsed by default + # no idea why, so, let's just modify the existing one + # new_section = Section(title=new_title, children=new_children) + item.title = new_title + item.children = new_children + new_items.append(item) + else: + new_items.append(item) + return new_items + + +def on_nav( + nav: Navigation, *, config: MkDocsConfig, files: Files, **kwargs: Any +) -> Navigation: + new_items = generate_renamed_section_items(nav.items, config=config) + return Navigation(items=new_items, pages=nav.pages) + + +def on_pre_page(page: Page, *, config: MkDocsConfig, files: Files) -> Page: + return page + + +def on_page_markdown( + markdown: str, *, page: Page, config: MkDocsConfig, files: Files +) -> str: + if isinstance(page.file, EnFile): + missing_translation_content = get_missing_translation_content(config.docs_dir) + header = "" + body = markdown + if markdown.startswith("#"): + header, _, body = markdown.partition("\n\n") + return f"{header}\n\n{missing_translation_content}\n\n{body}" + return markdown diff --git a/scripts/test-cov-html.sh b/scripts/test-cov-html.sh index 7957277fc3..d1bdfced2a 100755 --- a/scripts/test-cov-html.sh +++ b/scripts/test-cov-html.sh @@ -3,4 +3,7 @@ set -e set -x -bash scripts/test.sh --cov-report=html ${@} +bash scripts/test.sh ${@} +coverage combine +coverage report --show-missing +coverage html diff --git a/scripts/test.sh b/scripts/test.sh index d445ca1742..7d17add8fa 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -3,7 +3,5 @@ set -e set -x -# Check README.md is up to date -python ./scripts/docs.py verify-readme export PYTHONPATH=./docs_src -pytest --cov=fastapi --cov=tests --cov=docs_src --cov-report=term-missing:skip-covered --cov-report=xml tests ${@} +coverage run -m pytest tests ${@} diff --git a/scripts/zip-docs.sh b/scripts/zip-docs.sh deleted file mode 100644 index f2b7ba3be3..0000000000 --- a/scripts/zip-docs.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /usr/bin/env bash - -set -x -set -e - -if [ -f docs.zip ]; then - rm -rf docs.zip -fi -zip -r docs.zip ./site diff --git a/tests/main.py b/tests/main.py index d5603d0e61..15760c0396 100644 --- a/tests/main.py +++ b/tests/main.py @@ -1,5 +1,5 @@ import http -from typing import Optional +from typing import FrozenSet, Optional from fastapi import FastAPI, Path, Query @@ -49,97 +49,92 @@ def get_bool_id(item_id: bool): @app.get("/path/param/{item_id}") -def get_path_param_id(item_id: Optional[str] = Path(None)): - return item_id - - -@app.get("/path/param-required/{item_id}") -def get_path_param_required_id(item_id: str = Path(...)): +def get_path_param_id(item_id: Optional[str] = Path()): return item_id @app.get("/path/param-minlength/{item_id}") -def get_path_param_min_length(item_id: str = Path(..., min_length=3)): +def get_path_param_min_length(item_id: str = Path(min_length=3)): return item_id @app.get("/path/param-maxlength/{item_id}") -def get_path_param_max_length(item_id: str = Path(..., max_length=3)): +def get_path_param_max_length(item_id: str = Path(max_length=3)): return item_id @app.get("/path/param-min_maxlength/{item_id}") -def get_path_param_min_max_length(item_id: str = Path(..., max_length=3, min_length=2)): +def get_path_param_min_max_length(item_id: str = Path(max_length=3, min_length=2)): return item_id @app.get("/path/param-gt/{item_id}") -def get_path_param_gt(item_id: float = Path(..., gt=3)): +def get_path_param_gt(item_id: float = Path(gt=3)): return item_id @app.get("/path/param-gt0/{item_id}") -def get_path_param_gt0(item_id: float = Path(..., gt=0)): +def get_path_param_gt0(item_id: float = Path(gt=0)): return item_id @app.get("/path/param-ge/{item_id}") -def get_path_param_ge(item_id: float = Path(..., ge=3)): +def get_path_param_ge(item_id: float = Path(ge=3)): return item_id @app.get("/path/param-lt/{item_id}") -def get_path_param_lt(item_id: float = Path(..., lt=3)): +def get_path_param_lt(item_id: float = Path(lt=3)): return item_id @app.get("/path/param-lt0/{item_id}") -def get_path_param_lt0(item_id: float = Path(..., lt=0)): +def get_path_param_lt0(item_id: float = Path(lt=0)): return item_id @app.get("/path/param-le/{item_id}") -def get_path_param_le(item_id: float = Path(..., le=3)): +def get_path_param_le(item_id: float = Path(le=3)): return item_id @app.get("/path/param-lt-gt/{item_id}") -def get_path_param_lt_gt(item_id: float = Path(..., lt=3, gt=1)): +def get_path_param_lt_gt(item_id: float = Path(lt=3, gt=1)): return item_id @app.get("/path/param-le-ge/{item_id}") -def get_path_param_le_ge(item_id: float = Path(..., le=3, ge=1)): +def get_path_param_le_ge(item_id: float = Path(le=3, ge=1)): return item_id @app.get("/path/param-lt-int/{item_id}") -def get_path_param_lt_int(item_id: int = Path(..., lt=3)): +def get_path_param_lt_int(item_id: int = Path(lt=3)): return item_id @app.get("/path/param-gt-int/{item_id}") -def get_path_param_gt_int(item_id: int = Path(..., gt=3)): +def get_path_param_gt_int(item_id: int = Path(gt=3)): return item_id @app.get("/path/param-le-int/{item_id}") -def get_path_param_le_int(item_id: int = Path(..., le=3)): +def get_path_param_le_int(item_id: int = Path(le=3)): return item_id @app.get("/path/param-ge-int/{item_id}") -def get_path_param_ge_int(item_id: int = Path(..., ge=3)): +def get_path_param_ge_int(item_id: int = Path(ge=3)): return item_id @app.get("/path/param-lt-gt-int/{item_id}") -def get_path_param_lt_gt_int(item_id: int = Path(..., lt=3, gt=1)): +def get_path_param_lt_gt_int(item_id: int = Path(lt=3, gt=1)): return item_id @app.get("/path/param-le-ge-int/{item_id}") -def get_path_param_le_ge_int(item_id: int = Path(..., le=3, ge=1)): +def get_path_param_le_ge_int(item_id: int = Path(le=3, ge=1)): return item_id @@ -173,22 +168,27 @@ def get_query_type_int_default(query: int = 10): @app.get("/query/param") -def get_query_param(query=Query(None)): +def get_query_param(query=Query(default=None)): if query is None: return "foo bar" return f"foo bar {query}" @app.get("/query/param-required") -def get_query_param_required(query=Query(...)): +def get_query_param_required(query=Query()): return f"foo bar {query}" @app.get("/query/param-required/int") -def get_query_param_required_type(query: int = Query(...)): +def get_query_param_required_type(query: int = Query()): return f"foo bar {query}" @app.get("/enum-status-code", status_code=http.HTTPStatus.CREATED) def get_enum_status_code(): return "foo bar" + + +@app.get("/query/frozenset") +def get_query_type_frozenset(query: FrozenSet[int] = Query(...)): + return ",".join(map(str, sorted(query))) diff --git a/tests/test_additional_properties.py b/tests/test_additional_properties.py index 016c1f734e..516a569e42 100644 --- a/tests/test_additional_properties.py +++ b/tests/test_additional_properties.py @@ -19,92 +19,91 @@ def foo(items: Items): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/foo": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Foo", - "operationId": "foo_foo_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Items"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Items": { - "title": "Items", - "required": ["items"], - "type": "object", - "properties": { - "items": { - "title": "Items", - "type": "object", - "additionalProperties": {"type": "integer"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_additional_properties_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_additional_properties_post(): response = client.post("/foo", json={"items": {"foo": 1, "bar": 2}}) assert response.status_code == 200, response.text assert response.json() == {"foo": 1, "bar": 2} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/foo": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Foo", + "operationId": "foo_foo_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Items"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Items": { + "title": "Items", + "required": ["items"], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "object", + "additionalProperties": {"type": "integer"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_additional_response_extra.py b/tests/test_additional_response_extra.py index 1df1891e05..d62638c8fa 100644 --- a/tests/test_additional_response_extra.py +++ b/tests/test_additional_response_extra.py @@ -17,36 +17,33 @@ router.include_router(sub_router, prefix="/items") app.include_router(router) - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Item", - "operationId": "read_item_items__get", - } - } - }, -} - client = TestClient(app) -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_path_operation(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == {"id": "foo"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Item", + "operationId": "read_item_items__get", + } + } + }, + } diff --git a/tests/test_additional_responses_custom_model_in_callback.py b/tests/test_additional_responses_custom_model_in_callback.py index a1072cc569..5c08eaa6d2 100644 --- a/tests/test_additional_responses_custom_model_in_callback.py +++ b/tests/test_additional_responses_custom_model_in_callback.py @@ -25,114 +25,116 @@ def main_route(callback_url: HttpUrl): pass # pragma: no cover -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Main Route", - "operationId": "main_route__post", - "parameters": [ - { - "required": True, - "schema": { - "title": "Callback Url", - "maxLength": 2083, - "minLength": 1, - "type": "string", - "format": "uri", - }, - "name": "callback_url", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "callbacks": { - "callback_route": { - "{$callback_url}/callback/": { - "get": { - "summary": "Callback Route", - "operationId": "callback_route__callback_url__callback__get", - "responses": { - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomModel" - } - } - }, - "description": "Bad Request", - }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - } - } - } - }, - } - } - }, - "components": { - "schemas": { - "CustomModel": { - "title": "CustomModel", - "required": ["a"], - "type": "object", - "properties": {"a": {"title": "A", "type": "integer"}}, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - client = TestClient(app) def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Main Route", + "operationId": "main_route__post", + "parameters": [ + { + "required": True, + "schema": { + "title": "Callback Url", + "maxLength": 2083, + "minLength": 1, + "type": "string", + "format": "uri", + }, + "name": "callback_url", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "callbacks": { + "callback_route": { + "{$callback_url}/callback/": { + "get": { + "summary": "Callback Route", + "operationId": "callback_route__callback_url__callback__get", + "responses": { + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomModel" + } + } + }, + "description": "Bad Request", + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + }, + } + } + } + }, + } + } + }, + "components": { + "schemas": { + "CustomModel": { + "title": "CustomModel", + "required": ["a"], + "type": "object", + "properties": {"a": {"title": "A", "type": "integer"}}, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_additional_responses_custom_validationerror.py b/tests/test_additional_responses_custom_validationerror.py index 811fe69221..0526027685 100644 --- a/tests/test_additional_responses_custom_validationerror.py +++ b/tests/test_additional_responses_custom_validationerror.py @@ -30,71 +30,70 @@ async def a(id): pass # pragma: no cover -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a/{id}": { - "get": { - "responses": { - "422": { - "description": "Error", - "content": { - "application/vnd.api+json": { - "schema": {"$ref": "#/components/schemas/JsonApiError"} - } - }, - }, - "200": { - "description": "Successful Response", - "content": {"application/vnd.api+json": {"schema": {}}}, - }, - }, - "summary": "A", - "operationId": "a_a__id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Id"}, - "name": "id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, -} - - client = TestClient(app) def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/{id}": { + "get": { + "responses": { + "422": { + "description": "Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/vnd.api+json": {"schema": {}}}, + }, + }, + "summary": "A", + "operationId": "a_a__id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Id"}, + "name": "id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } + }, + }, + } + }, + } diff --git a/tests/test_additional_responses_default_validationerror.py b/tests/test_additional_responses_default_validationerror.py index cabb536d71..58de46ff60 100644 --- a/tests/test_additional_responses_default_validationerror.py +++ b/tests/test_additional_responses_default_validationerror.py @@ -9,77 +9,76 @@ async def a(id): pass # pragma: no cover -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a/{id}": { - "get": { - "responses": { - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "A", - "operationId": "a_a__id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Id"}, - "name": "id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - client = TestClient(app) def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/{id}": { + "get": { + "responses": { + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "A", + "operationId": "a_a__id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Id"}, + "name": "id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_additional_responses_response_class.py b/tests/test_additional_responses_response_class.py index aa549b1636..6746760f0e 100644 --- a/tests/test_additional_responses_response_class.py +++ b/tests/test_additional_responses_response_class.py @@ -35,83 +35,82 @@ async def b(): pass # pragma: no cover -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/vnd.api+json": { - "schema": {"$ref": "#/components/schemas/JsonApiError"} - } - }, - }, - "200": { - "description": "Successful Response", - "content": {"application/vnd.api+json": {"schema": {}}}, - }, - }, - "summary": "A", - "operationId": "a_a_get", - } - }, - "/b": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Error"} - } - }, - }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "B", - "operationId": "b_b_get", - } - }, - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, -} - - client = TestClient(app) def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/vnd.api+json": {"schema": {}}}, + }, + }, + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "B", + "operationId": "b_b_get", + } + }, + }, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } + }, + }, + } + }, + } diff --git a/tests/test_additional_responses_router.py b/tests/test_additional_responses_router.py index d2b73058f7..58d54b733e 100644 --- a/tests/test_additional_responses_router.py +++ b/tests/test_additional_responses_router.py @@ -1,5 +1,11 @@ from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient +from pydantic import BaseModel + + +class ResponseModel(BaseModel): + message: str + app = FastAPI() router = APIRouter() @@ -33,66 +39,24 @@ async def c(): return "c" +@router.get( + "/d", + responses={ + "400": {"description": "Error with str"}, + "5XX": {"model": ResponseModel}, + "default": {"model": ResponseModel}, + }, +) +async def d(): + return "d" + + app.include_router(router) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a": { - "get": { - "responses": { - "501": {"description": "Error 1"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "A", - "operationId": "a_a_get", - } - }, - "/b": { - "get": { - "responses": { - "502": {"description": "Error 2"}, - "4XX": {"description": "Error with range, upper"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "B", - "operationId": "b_b_get", - } - }, - "/c": { - "get": { - "responses": { - "400": {"description": "Error with str"}, - "5XX": {"description": "Error with range, lower"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "default": {"description": "A default response"}, - }, - "summary": "C", - "operationId": "c_c_get", - } - }, - }, -} client = TestClient(app) -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_a(): response = client.get("/a") assert response.status_code == 200, response.text @@ -109,3 +73,105 @@ def test_c(): response = client.get("/c") assert response.status_code == 200, response.text assert response.json() == "c" + + +def test_d(): + response = client.get("/d") + assert response.status_code == 200, response.text + assert response.json() == "d" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "501": {"description": "Error 1"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "502": {"description": "Error 2"}, + "4XX": {"description": "Error with range, upper"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "B", + "operationId": "b_b_get", + } + }, + "/c": { + "get": { + "responses": { + "400": {"description": "Error with str"}, + "5XX": {"description": "Error with range, lower"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "default": {"description": "A default response"}, + }, + "summary": "C", + "operationId": "c_c_get", + } + }, + "/d": { + "get": { + "responses": { + "400": {"description": "Error with str"}, + "5XX": { + "description": "Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "default": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + }, + }, + }, + "summary": "D", + "operationId": "d_d_get", + } + }, + }, + "components": { + "schemas": { + "ResponseModel": { + "title": "ResponseModel", + "required": ["message"], + "type": "object", + "properties": {"message": {"title": "Message", "type": "string"}}, + } + } + }, + } diff --git a/tests/test_ambiguous_params.py b/tests/test_ambiguous_params.py new file mode 100644 index 0000000000..42bcc27a1d --- /dev/null +++ b/tests/test_ambiguous_params.py @@ -0,0 +1,66 @@ +import pytest +from fastapi import Depends, FastAPI, Path +from fastapi.param_functions import Query +from typing_extensions import Annotated + +app = FastAPI() + + +def test_no_annotated_defaults(): + with pytest.raises( + AssertionError, match="Path parameters cannot have a default value" + ): + + @app.get("/items/{item_id}/") + async def get_item(item_id: Annotated[int, Path(default=1)]): + pass # pragma: nocover + + with pytest.raises( + AssertionError, + match=( + "`Query` default value cannot be set in `Annotated` for 'item_id'. Set the" + " default value with `=` instead." + ), + ): + + @app.get("/") + async def get(item_id: Annotated[int, Query(default=1)]): + pass # pragma: nocover + + +def test_no_multiple_annotations(): + async def dep(): + pass # pragma: nocover + + with pytest.raises( + AssertionError, + match="Cannot specify multiple `Annotated` FastAPI arguments for 'foo'", + ): + + @app.get("/") + async def get(foo: Annotated[int, Query(min_length=1), Query()]): + pass # pragma: nocover + + with pytest.raises( + AssertionError, + match=( + "Cannot specify `Depends` in `Annotated` and default value" + " together for 'foo'" + ), + ): + + @app.get("/") + async def get2(foo: Annotated[int, Depends(dep)] = Depends(dep)): + pass # pragma: nocover + + with pytest.raises( + AssertionError, + match=( + "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a" + " default value together for 'foo'" + ), + ): + + @app.get("/") + async def get3(foo: Annotated[int, Query(min_length=1)] = Depends(dep)): + pass # pragma: nocover diff --git a/tests/test_annotated.py b/tests/test_annotated.py new file mode 100644 index 0000000000..a4f42b038f --- /dev/null +++ b/tests/test_annotated.py @@ -0,0 +1,288 @@ +import pytest +from fastapi import APIRouter, FastAPI, Query +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +app = FastAPI() + + +@app.get("/default") +async def default(foo: Annotated[str, Query()] = "foo"): + return {"foo": foo} + + +@app.get("/required") +async def required(foo: Annotated[str, Query(min_length=1)]): + return {"foo": foo} + + +@app.get("/multiple") +async def multiple(foo: Annotated[str, object(), Query(min_length=1)]): + return {"foo": foo} + + +@app.get("/unrelated") +async def unrelated(foo: Annotated[str, object()]): + return {"foo": foo} + + +client = TestClient(app) + +foo_is_missing = { + "detail": [ + { + "loc": ["query", "foo"], + "msg": "field required", + "type": "value_error.missing", + } + ] +} +foo_is_short = { + "detail": [ + { + "ctx": {"limit_value": 1}, + "loc": ["query", "foo"], + "msg": "ensure this value has at least 1 characters", + "type": "value_error.any_str.min_length", + } + ] +} + + +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ("/default", 200, {"foo": "foo"}), + ("/default?foo=bar", 200, {"foo": "bar"}), + ("/required?foo=bar", 200, {"foo": "bar"}), + ("/required", 422, foo_is_missing), + ("/required?foo=", 422, foo_is_short), + ("/multiple?foo=bar", 200, {"foo": "bar"}), + ("/multiple", 422, foo_is_missing), + ("/multiple?foo=", 422, foo_is_short), + ("/unrelated?foo=bar", 200, {"foo": "bar"}), + ("/unrelated", 422, foo_is_missing), + ], +) +def test_get(path, expected_status, expected_response): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_multiple_path(): + app = FastAPI() + + @app.get("/test1") + @app.get("/test2") + async def test(var: Annotated[str, Query()] = "bar"): + return {"foo": var} + + client = TestClient(app) + response = client.get("/test1") + assert response.status_code == 200 + assert response.json() == {"foo": "bar"} + + response = client.get("/test1", params={"var": "baz"}) + assert response.status_code == 200 + assert response.json() == {"foo": "baz"} + + response = client.get("/test2") + assert response.status_code == 200 + assert response.json() == {"foo": "bar"} + + response = client.get("/test2", params={"var": "baz"}) + assert response.status_code == 200 + assert response.json() == {"foo": "baz"} + + +def test_nested_router(): + app = FastAPI() + + router = APIRouter(prefix="/nested") + + @router.get("/test") + async def test(var: Annotated[str, Query()] = "bar"): + return {"foo": var} + + app.include_router(router) + + client = TestClient(app) + + response = client.get("/nested/test") + assert response.status_code == 200 + assert response.json() == {"foo": "bar"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/default": { + "get": { + "summary": "Default", + "operationId": "default_default_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Foo", + "type": "string", + "default": "foo", + }, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/required": { + "get": { + "summary": "Required", + "operationId": "required_required_get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Foo", + "minLength": 1, + "type": "string", + }, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/multiple": { + "get": { + "summary": "Multiple", + "operationId": "multiple_multiple_get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Foo", + "minLength": 1, + "type": "string", + }, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/unrelated": { + "get": { + "summary": "Unrelated", + "operationId": "unrelated_unrelated_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Foo", "type": "string"}, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_application.py b/tests/test_application.py index d9194c15c7..e5f2f43878 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -5,1123 +5,6 @@ from .main import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/api_route": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Non Operation", - "operationId": "non_operation_api_route_get", - } - }, - "/non_decorated_route": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Non Decorated Route", - "operationId": "non_decorated_route_non_decorated_route_get", - } - }, - "/text": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get Text", - "operationId": "get_text_text_get", - } - }, - "/path/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Id", - "operationId": "get_id_path__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/str/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Str Id", - "operationId": "get_str_id_path_str__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Int Id", - "operationId": "get_int_id_path_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/float/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Float Id", - "operationId": "get_float_id_path_float__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "number"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/bool/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Bool Id", - "operationId": "get_bool_id_path_bool__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "boolean"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Id", - "operationId": "get_path_param_id_path_param__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-required/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Required Id", - "operationId": "get_path_param_required_id_path_param_required__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-minlength/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Min Length", - "operationId": "get_path_param_min_length_path_param_minlength__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "minLength": 3, - "type": "string", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-maxlength/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Max Length", - "operationId": "get_path_param_max_length_path_param_maxlength__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maxLength": 3, - "type": "string", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-min_maxlength/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Min Max Length", - "operationId": "get_path_param_min_max_length_path_param_min_maxlength__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maxLength": 3, - "minLength": 2, - "type": "string", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-gt/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Gt", - "operationId": "get_path_param_gt_path_param_gt__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMinimum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-gt0/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Gt0", - "operationId": "get_path_param_gt0_path_param_gt0__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMinimum": 0.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-ge/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Ge", - "operationId": "get_path_param_ge_path_param_ge__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "minimum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt", - "operationId": "get_path_param_lt_path_param_lt__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt0/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt0", - "operationId": "get_path_param_lt0_path_param_lt0__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 0.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le", - "operationId": "get_path_param_le_path_param_le__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt-gt/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt Gt", - "operationId": "get_path_param_lt_gt_path_param_lt_gt__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "exclusiveMinimum": 1.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le-ge/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le Ge", - "operationId": "get_path_param_le_ge_path_param_le_ge__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "minimum": 1.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt Int", - "operationId": "get_path_param_lt_int_path_param_lt_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-gt-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Gt Int", - "operationId": "get_path_param_gt_int_path_param_gt_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMinimum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le Int", - "operationId": "get_path_param_le_int_path_param_le_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-ge-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Ge Int", - "operationId": "get_path_param_ge_int_path_param_ge_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "minimum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt-gt-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt Gt Int", - "operationId": "get_path_param_lt_gt_int_path_param_lt_gt_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "exclusiveMinimum": 1.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le-ge-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le Ge Int", - "operationId": "get_path_param_le_ge_int_path_param_le_ge_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "minimum": 1.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/query": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query", - "operationId": "get_query_query_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/optional": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Optional", - "operationId": "get_query_optional_query_optional_get", - "parameters": [ - { - "required": False, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/int": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Type", - "operationId": "get_query_type_query_int_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query", "type": "integer"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/int/optional": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Type Optional", - "operationId": "get_query_type_optional_query_int_optional_get", - "parameters": [ - { - "required": False, - "schema": {"title": "Query", "type": "integer"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/int/default": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Type Int Default", - "operationId": "get_query_type_int_default_query_int_default_get", - "parameters": [ - { - "required": False, - "schema": {"title": "Query", "type": "integer", "default": 10}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/param": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Param", - "operationId": "get_query_param_query_param_get", - "parameters": [ - { - "required": False, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/param-required": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Param Required", - "operationId": "get_query_param_required_query_param_required_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/param-required/int": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Param Required Type", - "operationId": "get_query_param_required_type_query_param_required_int_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query", "type": "integer"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/enum-status-code": { - "get": { - "responses": { - "201": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "Get Enum Status Code", - "operationId": "get_enum_status_code_enum_status_code_get", - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.mark.parametrize( "path,expected_status,expected_response", @@ -1129,7 +12,6 @@ openapi_schema = { ("/api_route", 200, {"message": "Hello World"}), ("/non_decorated_route", 200, {"message": "Hello World"}), ("/nonexistent", 404, {"detail": "Not Found"}), - ("/openapi.json", 200, openapi_schema), ], ) def test_get_path(path, expected_status, expected_response): @@ -1167,3 +49,1135 @@ def test_enum_status_code_response(): response = client.get("/enum-status-code") assert response.status_code == 201, response.text assert response.json() == "foo bar" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/api_route": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Non Operation", + "operationId": "non_operation_api_route_get", + } + }, + "/non_decorated_route": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Non Decorated Route", + "operationId": "non_decorated_route_non_decorated_route_get", + } + }, + "/text": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get Text", + "operationId": "get_text_text_get", + } + }, + "/path/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Id", + "operationId": "get_id_path__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/str/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Str Id", + "operationId": "get_str_id_path_str__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Int Id", + "operationId": "get_int_id_path_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/float/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Float Id", + "operationId": "get_float_id_path_float__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "number"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/bool/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Bool Id", + "operationId": "get_bool_id_path_bool__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "boolean"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Id", + "operationId": "get_path_param_id_path_param__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-minlength/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Min Length", + "operationId": "get_path_param_min_length_path_param_minlength__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "minLength": 3, + "type": "string", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-maxlength/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Max Length", + "operationId": "get_path_param_max_length_path_param_maxlength__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maxLength": 3, + "type": "string", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-min_maxlength/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Min Max Length", + "operationId": "get_path_param_min_max_length_path_param_min_maxlength__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maxLength": 3, + "minLength": 2, + "type": "string", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-gt/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Gt", + "operationId": "get_path_param_gt_path_param_gt__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMinimum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-gt0/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Gt0", + "operationId": "get_path_param_gt0_path_param_gt0__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMinimum": 0.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-ge/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Ge", + "operationId": "get_path_param_ge_path_param_ge__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "minimum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt", + "operationId": "get_path_param_lt_path_param_lt__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt0/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt0", + "operationId": "get_path_param_lt0_path_param_lt0__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 0.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le", + "operationId": "get_path_param_le_path_param_le__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt-gt/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt Gt", + "operationId": "get_path_param_lt_gt_path_param_lt_gt__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "exclusiveMinimum": 1.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le-ge/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le Ge", + "operationId": "get_path_param_le_ge_path_param_le_ge__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "minimum": 1.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt Int", + "operationId": "get_path_param_lt_int_path_param_lt_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-gt-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Gt Int", + "operationId": "get_path_param_gt_int_path_param_gt_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMinimum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le Int", + "operationId": "get_path_param_le_int_path_param_le_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-ge-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Ge Int", + "operationId": "get_path_param_ge_int_path_param_ge_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "minimum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt-gt-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt Gt Int", + "operationId": "get_path_param_lt_gt_int_path_param_lt_gt_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "exclusiveMinimum": 1.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le-ge-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le Ge Int", + "operationId": "get_path_param_le_ge_int_path_param_le_ge_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "minimum": 1.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/query": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query", + "operationId": "get_query_query_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/optional": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Optional", + "operationId": "get_query_optional_query_optional_get", + "parameters": [ + { + "required": False, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/int": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Type", + "operationId": "get_query_type_query_int_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query", "type": "integer"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/int/optional": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Type Optional", + "operationId": "get_query_type_optional_query_int_optional_get", + "parameters": [ + { + "required": False, + "schema": {"title": "Query", "type": "integer"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/int/default": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Type Int Default", + "operationId": "get_query_type_int_default_query_int_default_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Query", + "type": "integer", + "default": 10, + }, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/param": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Param", + "operationId": "get_query_param_query_param_get", + "parameters": [ + { + "required": False, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/param-required": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Param Required", + "operationId": "get_query_param_required_query_param_required_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/param-required/int": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Param Required Type", + "operationId": "get_query_param_required_type_query_param_required_int_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query", "type": "integer"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/enum-status-code": { + "get": { + "responses": { + "201": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "Get Enum Status Code", + "operationId": "get_enum_status_code_enum_status_code_get", + } + }, + "/query/frozenset": { + "get": { + "summary": "Get Query Type Frozenset", + "operationId": "get_query_type_frozenset_query_frozenset_get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Query", + "uniqueItems": True, + "type": "array", + "items": {"type": "integer"}, + }, + "name": "query", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_custom_middleware_exception.py b/tests/test_custom_middleware_exception.py new file mode 100644 index 0000000000..d9b81e7c2e --- /dev/null +++ b/tests/test_custom_middleware_exception.py @@ -0,0 +1,95 @@ +from pathlib import Path +from typing import Optional + +from fastapi import APIRouter, FastAPI, File, UploadFile +from fastapi.exceptions import HTTPException +from fastapi.testclient import TestClient + +app = FastAPI() + +router = APIRouter() + + +class ContentSizeLimitMiddleware: + """Content size limiting middleware for ASGI applications + Args: + app (ASGI application): ASGI application + max_content_size (optional): the maximum content size allowed in bytes, None for no limit + """ + + def __init__(self, app: APIRouter, max_content_size: Optional[int] = None): + self.app = app + self.max_content_size = max_content_size + + def receive_wrapper(self, receive): + received = 0 + + async def inner(): + nonlocal received + message = await receive() + if message["type"] != "http.request": + return message # pragma: no cover + + body_len = len(message.get("body", b"")) + received += body_len + if received > self.max_content_size: + raise HTTPException( + 422, + detail={ + "name": "ContentSizeLimitExceeded", + "code": 999, + "message": "File limit exceeded", + }, + ) + return message + + return inner + + async def __call__(self, scope, receive, send): + if scope["type"] != "http" or self.max_content_size is None: + await self.app(scope, receive, send) + return + + wrapper = self.receive_wrapper(receive) + await self.app(scope, wrapper, send) + + +@router.post("/middleware") +def run_middleware(file: UploadFile = File(..., description="Big File")): + return {"message": "OK"} + + +app.include_router(router) +app.add_middleware(ContentSizeLimitMiddleware, max_content_size=2**8) + + +client = TestClient(app) + + +def test_custom_middleware_exception(tmp_path: Path): + default_pydantic_max_size = 2**16 + path = tmp_path / "test.txt" + path.write_bytes(b"x" * (default_pydantic_max_size + 1)) + + with client: + with open(path, "rb") as file: + response = client.post("/middleware", files={"file": file}) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": { + "name": "ContentSizeLimitExceeded", + "code": 999, + "message": "File limit exceeded", + } + } + + +def test_custom_middleware_exception_not_raised(tmp_path: Path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with client: + with open(path, "rb") as file: + response = client.post("/middleware", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "OK"} diff --git a/tests/test_custom_route_class.py b/tests/test_custom_route_class.py index 1a9ea7199a..d1b18ef1d2 100644 --- a/tests/test_custom_route_class.py +++ b/tests/test_custom_route_class.py @@ -46,49 +46,6 @@ app.include_router(router=router_a, prefix="/a") client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get A", - "operationId": "get_a_a__get", - } - }, - "/a/b/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get B", - "operationId": "get_b_a_b__get", - } - }, - "/a/b/c/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get C", - "operationId": "get_c_a_b_c__get", - } - }, - }, -} - @pytest.mark.parametrize( "path,expected_status,expected_response", @@ -96,7 +53,6 @@ openapi_schema = { ("/a", 200, {"msg": "A"}), ("/a/b", 200, {"msg": "B"}), ("/a/b/c", 200, {"msg": "C"}), - ("/openapi.json", 200, openapi_schema), ], ) def test_get_path(path, expected_status, expected_response): @@ -110,6 +66,53 @@ def test_route_classes(): for r in app.router.routes: assert isinstance(r, Route) routes[r.path] = r - assert getattr(routes["/a/"], "x_type") == "A" - assert getattr(routes["/a/b/"], "x_type") == "B" - assert getattr(routes["/a/b/c/"], "x_type") == "C" + assert getattr(routes["/a/"], "x_type") == "A" # noqa: B009 + assert getattr(routes["/a/b/"], "x_type") == "B" # noqa: B009 + assert getattr(routes["/a/b/c/"], "x_type") == "C" # noqa: B009 + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get A", + "operationId": "get_a_a__get", + } + }, + "/a/b/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get B", + "operationId": "get_b_a_b__get", + } + }, + "/a/b/c/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get C", + "operationId": "get_c_a_b_c__get", + } + }, + }, + } diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 43f1a116cb..2e6217d34e 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -1,6 +1,10 @@ +from pathlib import Path +from typing import List + import pytest -from fastapi import UploadFile +from fastapi import FastAPI, UploadFile from fastapi.datastructures import Default +from fastapi.testclient import TestClient def test_upload_file_invalid(): @@ -20,3 +24,25 @@ def test_default_placeholder_bool(): placeholder_b = Default("") assert placeholder_a assert not placeholder_b + + +def test_upload_file_is_closed(tmp_path: Path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + app = FastAPI() + + testing_file_store: List[UploadFile] = [] + + @app.post("/uploadfile/") + def create_upload_file(file: UploadFile): + testing_file_store.append(file) + return {"filename": file.filename} + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + assert testing_file_store + assert testing_file_store[0].file.closed diff --git a/tests/test_dependency_cache.py b/tests/test_dependency_cache.py index 65ed7f9464..08fb9b74f4 100644 --- a/tests/test_dependency_cache.py +++ b/tests/test_dependency_cache.py @@ -1,4 +1,4 @@ -from fastapi import Depends, FastAPI +from fastapi import Depends, FastAPI, Security from fastapi.testclient import TestClient app = FastAPI() @@ -35,6 +35,19 @@ async def get_sub_counter_no_cache( return {"counter": count, "subcounter": subcount} +@app.get("/scope-counter") +async def get_scope_counter( + count: int = Security(dep_counter), + scope_count_1: int = Security(dep_counter, scopes=["scope"]), + scope_count_2: int = Security(dep_counter, scopes=["scope"]), +): + return { + "counter": count, + "scope_counter_1": scope_count_1, + "scope_counter_2": scope_count_2, + } + + client = TestClient(app) @@ -66,3 +79,13 @@ def test_sub_counter_no_cache(): response = client.get("/sub-counter-no-cache/") assert response.status_code == 200, response.text assert response.json() == {"counter": 4, "subcounter": 3} + + +def test_security_cache(): + counter_holder["counter"] = 0 + response = client.get("/scope-counter/") + assert response.status_code == 200, response.text + assert response.json() == {"counter": 1, "scope_counter_1": 2, "scope_counter_2": 2} + response = client.get("/scope-counter/") + assert response.status_code == 200, response.text + assert response.json() == {"counter": 3, "scope_counter_1": 4, "scope_counter_2": 4} diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py index 33899134e9..285fdf1ab7 100644 --- a/tests/test_dependency_duplicates.py +++ b/tests/test_dependency_duplicates.py @@ -44,156 +44,6 @@ async def no_duplicates_sub( return [item, sub_items] -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/with-duplicates": { - "post": { - "summary": "With Duplicates", - "operationId": "with_duplicates_with_duplicates_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/no-duplicates": { - "post": { - "summary": "No Duplicates", - "operationId": "no_duplicates_no_duplicates_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_no_duplicates_no_duplicates_post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/with-duplicates-sub": { - "post": { - "summary": "No Duplicates Sub", - "operationId": "no_duplicates_sub_with_duplicates_sub_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_no_duplicates_no_duplicates_post": { - "title": "Body_no_duplicates_no_duplicates_post", - "required": ["item", "item2"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["data"], - "type": "object", - "properties": {"data": {"title": "Data", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_no_duplicates_invalid(): response = client.post("/no-duplicates", json={"item": {"data": "myitem"}}) assert response.status_code == 422, response.text @@ -230,3 +80,152 @@ def test_sub_duplicates(): {"data": "myitem"}, [{"data": "myitem"}, {"data": "myitem"}], ] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/with-duplicates": { + "post": { + "summary": "With Duplicates", + "operationId": "with_duplicates_with_duplicates_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/no-duplicates": { + "post": { + "summary": "No Duplicates", + "operationId": "no_duplicates_no_duplicates_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_no_duplicates_no_duplicates_post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/with-duplicates-sub": { + "post": { + "summary": "No Duplicates Sub", + "operationId": "no_duplicates_sub_with_duplicates_sub_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_no_duplicates_no_duplicates_post": { + "title": "Body_no_duplicates_no_duplicates_post", + "required": ["item", "item2"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["data"], + "type": "object", + "properties": {"data": {"title": "Data", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_dependency_normal_exceptions.py b/tests/test_dependency_normal_exceptions.py index 49a19f460c..23c366d5d7 100644 --- a/tests/test_dependency_normal_exceptions.py +++ b/tests/test_dependency_normal_exceptions.py @@ -26,14 +26,14 @@ async def get_database(): @app.put("/invalid-user/{user_id}") def put_invalid_user( - user_id: str, name: str = Body(...), db: dict = Depends(get_database) + user_id: str, name: str = Body(), db: dict = Depends(get_database) ): db[user_id] = name raise HTTPException(status_code=400, detail="Invalid user") @app.put("/user/{user_id}") -def put_user(user_id: str, name: str = Body(...), db: dict = Depends(get_database)): +def put_user(user_id: str, name: str = Body(), db: dict = Depends(get_database)): db[user_id] = name return {"message": "OK"} diff --git a/tests/test_deprecated_openapi_prefix.py b/tests/test_deprecated_openapi_prefix.py index a3355256f9..688b9837f2 100644 --- a/tests/test_deprecated_openapi_prefix.py +++ b/tests/test_deprecated_openapi_prefix.py @@ -11,34 +11,32 @@ def read_main(request: Request): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, - "servers": [{"url": "/api/v1"}], -} - - -def test_openapi(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - def test_main(): response = client.get("/app") assert response.status_code == 200 assert response.json() == {"message": "Hello World", "root_path": "/api/v1"} + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + "servers": [{"url": "/api/v1"}], + } diff --git a/tests/test_duplicate_models_openapi.py b/tests/test_duplicate_models_openapi.py index f077dfea08..116b2006a0 100644 --- a/tests/test_duplicate_models_openapi.py +++ b/tests/test_duplicate_models_openapi.py @@ -23,60 +23,57 @@ def f(): return {"c": {}, "d": {"a": {}}} -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "summary": "F", - "operationId": "f__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model3"} - } - }, - } - }, - } - } - }, - "components": { - "schemas": { - "Model": {"title": "Model", "type": "object", "properties": {}}, - "Model2": { - "title": "Model2", - "required": ["a"], - "type": "object", - "properties": {"a": {"$ref": "#/components/schemas/Model"}}, - }, - "Model3": { - "title": "Model3", - "required": ["c", "d"], - "type": "object", - "properties": { - "c": {"$ref": "#/components/schemas/Model"}, - "d": {"$ref": "#/components/schemas/Model2"}, - }, - }, - } - }, -} - - client = TestClient(app) -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_get_api_route(): response = client.get("/") assert response.status_code == 200, response.text assert response.json() == {"c": {}, "d": {"a": {}}} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "F", + "operationId": "f__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model3"} + } + }, + } + }, + } + } + }, + "components": { + "schemas": { + "Model": {"title": "Model", "type": "object", "properties": {}}, + "Model2": { + "title": "Model2", + "required": ["a"], + "type": "object", + "properties": {"a": {"$ref": "#/components/schemas/Model"}}, + }, + "Model3": { + "title": "Model3", + "required": ["c", "d"], + "type": "object", + "properties": { + "c": {"$ref": "#/components/schemas/Model"}, + "d": {"$ref": "#/components/schemas/Model2"}, + }, + }, + } + }, + } diff --git a/tests/test_empty_router.py b/tests/test_empty_router.py index 186ceb347e..1a40cbe304 100644 --- a/tests/test_empty_router.py +++ b/tests/test_empty_router.py @@ -1,5 +1,6 @@ import pytest from fastapi import APIRouter, FastAPI +from fastapi.exceptions import FastAPIError from fastapi.testclient import TestClient app = FastAPI() @@ -31,5 +32,5 @@ def test_use_empty(): def test_include_empty(): # if both include and router.path are empty - it should raise exception - with pytest.raises(Exception): + with pytest.raises(FastAPIError): app.include_router(router) diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py new file mode 100644 index 0000000000..bf05aa5852 --- /dev/null +++ b/tests/test_enforce_once_required_parameter.py @@ -0,0 +1,111 @@ +from typing import Optional + +from fastapi import Depends, FastAPI, Query, status +from fastapi.testclient import TestClient + +app = FastAPI() + + +def _get_client_key(client_id: str = Query(...)) -> str: + return f"{client_id}_key" + + +def _get_client_tag(client_id: Optional[str] = Query(None)) -> Optional[str]: + if client_id is None: + return None + return f"{client_id}_tag" + + +@app.get("/foo") +def foo_handler( + client_key: str = Depends(_get_client_key), + client_tag: Optional[str] = Depends(_get_client_tag), +): + return {"client_id": client_key, "client_tag": client_tag} + + +client = TestClient(app) + +expected_schema = { + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "title": "Detail", + "type": "array", + } + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "loc": { + "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, + "title": "Location", + "type": "array", + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error " "Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", + }, + } + }, + "info": {"title": "FastAPI", "version": "0.1.0"}, + "openapi": "3.0.2", + "paths": { + "/foo": { + "get": { + "operationId": "foo_handler_foo_get", + "parameters": [ + { + "in": "query", + "name": "client_id", + "required": True, + "schema": {"title": "Client Id", "type": "string"}, + }, + ], + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful " "Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation " "Error", + }, + }, + "summary": "Foo Handler", + } + } + }, +} + + +def test_schema(): + response = client.get("/openapi.json") + assert response.status_code == status.HTTP_200_OK + actual_schema = response.json() + assert actual_schema == expected_schema + + +def test_get_invalid(): + response = client.get("/foo") + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + + +def test_get_valid(): + response = client.get("/foo", params={"client_id": "bar"}) + assert response.status_code == 200 + assert response.json() == {"client_id": "bar_key", "client_tag": "bar_tag"} diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py index 491ba61c68..c0db62c19d 100644 --- a/tests/test_extra_routes.py +++ b/tests/test_extra_routes.py @@ -52,273 +52,6 @@ def trace_item(item_id: str): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Items", - "operationId": "get_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "delete": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Delete Item", - "operationId": "delete_item_items__item_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - "options": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Options Item", - "operationId": "options_item_items__item_id__options", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "head": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Head Item", - "operationId": "head_item_items__item_id__head", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "patch": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Patch Item", - "operationId": "patch_item_items__item_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - "trace": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Trace Item", - "operationId": "trace_item_items__item_id__trace", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - }, - "/items-not-decorated/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Not Decorated", - "operationId": "get_not_decorated_items_not_decorated__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_api_route(): response = client.get("/items/foo") @@ -333,7 +66,7 @@ def test_get_api_route_not_decorated(): def test_delete(): - response = client.delete("/items/foo", json={"name": "Foo"}) + response = client.request("DELETE", "/items/foo", json={"name": "Foo"}) assert response.status_code == 200, response.text assert response.json() == {"item_id": "foo", "item": {"name": "Foo", "price": None}} @@ -360,3 +93,270 @@ def test_trace(): response = client.request("trace", "/items/foo") assert response.status_code == 200, response.text assert response.headers["content-type"] == "message/http" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Items", + "operationId": "get_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "delete": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Delete Item", + "operationId": "delete_item_items__item_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + }, + "options": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Options Item", + "operationId": "options_item_items__item_id__options", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "head": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Head Item", + "operationId": "head_item_items__item_id__head", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "patch": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Patch Item", + "operationId": "patch_item_items__item_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + }, + "trace": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Trace Item", + "operationId": "trace_item_items__item_id__trace", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + }, + "/items-not-decorated/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Not Decorated", + "operationId": "get_not_decorated_items_not_decorated__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_filter_pydantic_sub_model.py b/tests/test_filter_pydantic_sub_model.py index 8814356a10..15b15f8624 100644 --- a/tests/test_filter_pydantic_sub_model.py +++ b/tests/test_filter_pydantic_sub_model.py @@ -40,99 +40,6 @@ async def get_model_a(name: str, model_c=Depends(get_model_c)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/model/{name}": { - "get": { - "summary": "Get Model A", - "operationId": "get_model_a_model__name__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Name", "type": "string"}, - "name": "name", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ModelA"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ModelA": { - "title": "ModelA", - "required": ["name", "model_b"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "model_b": {"$ref": "#/components/schemas/ModelB"}, - }, - }, - "ModelB": { - "title": "ModelB", - "required": ["username"], - "type": "object", - "properties": {"username": {"title": "Username", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_filter_sub_model(): response = client.get("/model/modelA") assert response.status_code == 200, response.text @@ -153,3 +60,95 @@ def test_validator_is_cloned(): "type": "value_error", } ] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/model/{name}": { + "get": { + "summary": "Get Model A", + "operationId": "get_model_a_model__name__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Name", "type": "string"}, + "name": "name", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ModelA"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ModelA": { + "title": "ModelA", + "required": ["name", "model_b"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "model_b": {"$ref": "#/components/schemas/ModelB"}, + }, + }, + "ModelB": { + "title": "ModelB", + "required": ["username"], + "type": "object", + "properties": {"username": {"title": "Username", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_forms_from_non_typing_sequences.py b/tests/test_forms_from_non_typing_sequences.py index be917eab7e..52ce247533 100644 --- a/tests/test_forms_from_non_typing_sequences.py +++ b/tests/test_forms_from_non_typing_sequences.py @@ -5,17 +5,17 @@ app = FastAPI() @app.post("/form/python-list") -def post_form_param_list(items: list = Form(...)): +def post_form_param_list(items: list = Form()): return items @app.post("/form/python-set") -def post_form_param_set(items: set = Form(...)): +def post_form_param_set(items: set = Form()): return items @app.post("/form/python-tuple") -def post_form_param_tuple(items: tuple = Form(...)): +def post_form_param_tuple(items: tuple = Form()): return items diff --git a/tests/test_get_request_body.py b/tests/test_get_request_body.py index 88b9d839f5..541147fa8f 100644 --- a/tests/test_get_request_body.py +++ b/tests/test_get_request_body.py @@ -19,90 +19,89 @@ async def create_item(product: Product): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/product": { - "get": { - "summary": "Create Item", - "operationId": "create_item_product_get", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Product"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Product": { - "title": "Product", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} +def test_get_with_body(): + body = {"name": "Foo", "description": "Some description", "price": 5.5} + response = client.request("GET", "/product", json=body) + assert response.json() == body def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_get_with_body(): - body = {"name": "Foo", "description": "Some description", "price": 5.5} - response = client.get("/product", json=body) - assert response.json() == body + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/product": { + "get": { + "summary": "Create Item", + "operationId": "create_item_product_get", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Product"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Product": { + "title": "Product", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_include_router_defaults_overrides.py b/tests/test_include_router_defaults_overrides.py index ccb6c7229d..ced56c84d3 100644 --- a/tests/test_include_router_defaults_overrides.py +++ b/tests/test_include_router_defaults_overrides.py @@ -343,16 +343,6 @@ app.include_router(router2_default) client = TestClient(app) -def test_openapi(): - client = TestClient(app) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - response = client.get("/openapi.json") - assert issubclass(w[-1].category, UserWarning) - assert "Duplicate Operation ID" in str(w[-1].message) - assert response.json() == openapi_schema - - def test_level1_override(): response = client.get("/override1?level1=foo") assert response.json() == "foo" @@ -445,6179 +435,6863 @@ def test_paths_level5(override1, override2, override3, override4, override5): assert not override5 or "x-level5" in response.headers -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/override1": { - "get": { - "tags": ["path1a", "path1b"], - "summary": "Path1 Override", - "operationId": "path1_override_override1_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level1", "type": "string"}, - "name": "level1", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-1": {"schema": {}}}, +def test_openapi(): + client = TestClient(app) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + response = client.get("/openapi.json") + assert issubclass(w[-1].category, UserWarning) + assert "Duplicate Operation ID" in str(w[-1].message) + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/override1": { + "get": { + "tags": ["path1a", "path1b"], + "summary": "Path1 Override", + "operationId": "path1_override_override1_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level1", "type": "string"}, + "name": "level1", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-1": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, } } }, }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/default1": { - "get": { - "summary": "Path1 Default", - "operationId": "path1_default_default1_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level1", "type": "string"}, - "name": "level1", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-0": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } - }, - "/level1/level2/override3": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "path3a", - "path3b", - ], - "summary": "Path3 Override Router2 Override", - "operationId": "path3_override_router2_override_level1_level2_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/default3": { - "get": { - "tags": ["level1a", "level1b", "level2a", "level2b"], - "summary": "Path3 Default Router2 Override", - "operationId": "path3_default_router2_override_level1_level2_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level2_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/level4/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level2_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_level2_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - ], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_level2_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level2_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level4/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level2_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_level2_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/default5": { - "get": { - "tags": ["level1a", "level1b", "level2a", "level2b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_level2_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/override3": { - "get": { - "tags": ["level1a", "level1b", "path3a", "path3b"], - "summary": "Path3 Override Router2 Default", - "operationId": "path3_override_router2_default_level1_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/default3": { - "get": { - "tags": ["level1a", "level1b"], - "summary": "Path3 Default Router2 Default", - "operationId": "path3_default_router2_default_level1_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-1": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level1/level3/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level3/level4/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level3a", - "level3b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level3/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level3a", - "level3b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level3/default5": { - "get": { - "tags": ["level1a", "level1b", "level3a", "level3b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level1/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level4/default5": { - "get": { - "tags": ["level1a", "level1b", "level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/override5": { - "get": { - "tags": ["level1a", "level1b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/default5": { - "get": { - "tags": ["level1a", "level1b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-1": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": {"title": "Level1", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level2/override3": { - "get": { - "tags": ["level2a", "level2b", "path3a", "path3b"], - "summary": "Path3 Override Router2 Override", - "operationId": "path3_override_router2_override_level2_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/default3": { - "get": { - "tags": ["level2a", "level2b"], - "summary": "Path3 Default Router2 Override", - "operationId": "path3_default_router2_override_level2_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/level4/override5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level2_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/level4/default5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level2_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/override5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level3a", - "level3b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level2_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/default5": { - "get": { - "tags": ["level2a", "level2b", "level3a", "level3b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level2_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level4/override5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level2_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level4/default5": { - "get": { - "tags": ["level2a", "level2b", "level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level2_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/override5": { - "get": { - "tags": ["level2a", "level2b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level2_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/default5": { - "get": { - "tags": ["level2a", "level2b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level2_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": {"title": "Level2", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/override3": { - "get": { - "tags": ["path3a", "path3b"], - "summary": "Path3 Override Router2 Default", - "operationId": "path3_override_router2_default_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/default3": { - "get": { - "summary": "Path3 Default Router2 Default", - "operationId": "path3_default_router2_default_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-0": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } - }, - "/level3/level4/override5": { - "get": { - "tags": [ - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level3/level4/default5": { - "get": { - "tags": ["level3a", "level3b", "level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level3/override5": { - "get": { - "tags": ["level3a", "level3b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level3/default5": { - "get": { - "tags": ["level3a", "level3b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": {"title": "Level3", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level4/override5": { - "get": { - "tags": ["level4a", "level4b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level4/default5": { - "get": { - "tags": ["level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": {"title": "Level4", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/override5": { - "get": { - "tags": ["path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": {"title": "Level5", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/default5": { - "get": { - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-0": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": {"title": "Level0", "type": "string"}, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + "deprecated": True, + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, + "/default1": { + "get": { + "summary": "Path1 Default", + "operationId": "path1_default_default1_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level1", "type": "string"}, + "name": "level1", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-0": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + } + }, + } }, - } - }, -} + "/level1/level2/override3": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "path3a", + "path3b", + ], + "summary": "Path3 Override Router2 Override", + "operationId": "path3_override_router2_override_level1_level2_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/default3": { + "get": { + "tags": ["level1a", "level1b", "level2a", "level2b"], + "summary": "Path3 Default Router2 Override", + "operationId": "path3_default_router2_override_level1_level2_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level2_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/level4/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level2_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_level2_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + ], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_level2_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level2_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level4/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level2_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_level2_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/default5": { + "get": { + "tags": ["level1a", "level1b", "level2a", "level2b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_level2_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/override3": { + "get": { + "tags": ["level1a", "level1b", "path3a", "path3b"], + "summary": "Path3 Override Router2 Default", + "operationId": "path3_override_router2_default_level1_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/default3": { + "get": { + "tags": ["level1a", "level1b"], + "summary": "Path3 Default Router2 Default", + "operationId": "path3_default_router2_default_level1_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-1": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level1/level3/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level3/level4/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level3a", + "level3b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level3/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level3a", + "level3b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level3/default5": { + "get": { + "tags": ["level1a", "level1b", "level3a", "level3b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level1/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level4/default5": { + "get": { + "tags": ["level1a", "level1b", "level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/override5": { + "get": { + "tags": ["level1a", "level1b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/default5": { + "get": { + "tags": ["level1a", "level1b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-1": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level2/override3": { + "get": { + "tags": ["level2a", "level2b", "path3a", "path3b"], + "summary": "Path3 Override Router2 Override", + "operationId": "path3_override_router2_override_level2_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/default3": { + "get": { + "tags": ["level2a", "level2b"], + "summary": "Path3 Default Router2 Override", + "operationId": "path3_default_router2_override_level2_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/level4/override5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level2_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/level4/default5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level2_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/override5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level3a", + "level3b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level2_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/default5": { + "get": { + "tags": ["level2a", "level2b", "level3a", "level3b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level2_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level4/override5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level2_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level4/default5": { + "get": { + "tags": ["level2a", "level2b", "level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level2_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/override5": { + "get": { + "tags": ["level2a", "level2b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level2_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/default5": { + "get": { + "tags": ["level2a", "level2b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level2_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/override3": { + "get": { + "tags": ["path3a", "path3b"], + "summary": "Path3 Override Router2 Default", + "operationId": "path3_override_router2_default_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/default3": { + "get": { + "summary": "Path3 Default Router2 Default", + "operationId": "path3_default_router2_default_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-0": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + } + }, + } + }, + "/level3/level4/override5": { + "get": { + "tags": [ + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level3/level4/default5": { + "get": { + "tags": ["level3a", "level3b", "level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level3/override5": { + "get": { + "tags": ["level3a", "level3b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level3/default5": { + "get": { + "tags": ["level3a", "level3b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level4/override5": { + "get": { + "tags": ["level4a", "level4b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level4/default5": { + "get": { + "tags": ["level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/override5": { + "get": { + "tags": ["path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/default5": { + "get": { + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-0": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + } + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_invalid_sequence_param.py b/tests/test_invalid_sequence_param.py index f00dd7b934..475786adbf 100644 --- a/tests/test_invalid_sequence_param.py +++ b/tests/test_invalid_sequence_param.py @@ -13,7 +13,7 @@ def test_invalid_sequence(): title: str @app.get("/items/") - def read_items(q: List[Item] = Query(None)): + def read_items(q: List[Item] = Query(default=None)): pass # pragma: no cover @@ -25,7 +25,7 @@ def test_invalid_tuple(): title: str @app.get("/items/") - def read_items(q: Tuple[Item, Item] = Query(None)): + def read_items(q: Tuple[Item, Item] = Query(default=None)): pass # pragma: no cover @@ -37,7 +37,7 @@ def test_invalid_dict(): title: str @app.get("/items/") - def read_items(q: Dict[str, Item] = Query(None)): + def read_items(q: Dict[str, Item] = Query(default=None)): pass # pragma: no cover @@ -49,5 +49,5 @@ def test_invalid_simple_dict(): title: str @app.get("/items/") - def read_items(q: Optional[dict] = Query(None)): + def read_items(q: Optional[dict] = Query(default=None)): pass # pragma: no cover diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index fa82b5ea83..f4fdcf6014 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from datetime import datetime, timezone from enum import Enum from pathlib import PurePath, PurePosixPath, PureWindowsPath @@ -19,6 +20,12 @@ class Pet: self.name = name +@dataclass +class Item: + name: str + count: int + + class DictablePerson(Person): def __iter__(self): return ((k, v) for k, v in self.__dict__.items()) @@ -67,7 +74,7 @@ class ModelWithConfig(BaseModel): class ModelWithAlias(BaseModel): - foo: str = Field(..., alias="Foo") + foo: str = Field(alias="Foo") class ModelWithDefault(BaseModel): @@ -93,16 +100,51 @@ def fixture_model_with_path(request): return ModelWithPath(path=request.param("/foo", "bar")) +def test_encode_dict(): + pet = {"name": "Firulais", "owner": {"name": "Foo"}} + assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} + assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} + assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} + assert jsonable_encoder(pet, include={}) == {} + assert jsonable_encoder(pet, exclude={}) == { + "name": "Firulais", + "owner": {"name": "Foo"}, + } + + def test_encode_class(): person = Person(name="Foo") pet = Pet(owner=person, name="Firulais") assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} + assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} + assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} + assert jsonable_encoder(pet, include={}) == {} + assert jsonable_encoder(pet, exclude={}) == { + "name": "Firulais", + "owner": {"name": "Foo"}, + } def test_encode_dictable(): person = DictablePerson(name="Foo") pet = DictablePet(owner=person, name="Firulais") assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} + assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} + assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} + assert jsonable_encoder(pet, include={}) == {} + assert jsonable_encoder(pet, exclude={}) == { + "name": "Firulais", + "owner": {"name": "Foo"}, + } + + +def test_encode_dataclass(): + item = Item(name="foo", count=100) + assert jsonable_encoder(item) == {"name": "foo", "count": 100} + assert jsonable_encoder(item, include={"name"}) == {"name": "foo"} + assert jsonable_encoder(item, exclude={"count"}) == {"name": "foo"} + assert jsonable_encoder(item, include={}) == {} + assert jsonable_encoder(item, exclude={}) == {"name": "foo", "count": 100} def test_encode_unsupported(): @@ -144,6 +186,14 @@ def test_encode_model_with_default(): assert jsonable_encoder(model, exclude_unset=True, exclude_defaults=True) == { "foo": "foo" } + assert jsonable_encoder(model, include={"foo"}) == {"foo": "foo"} + assert jsonable_encoder(model, exclude={"bla"}) == {"foo": "foo", "bar": "bar"} + assert jsonable_encoder(model, include={}) == {} + assert jsonable_encoder(model, exclude={}) == { + "foo": "foo", + "bar": "bar", + "bla": "bla", + } def test_custom_encoders(): diff --git a/tests/test_modules_same_name_body/app/a.py b/tests/test_modules_same_name_body/app/a.py index 3c86c1865e..3772368906 100644 --- a/tests/test_modules_same_name_body/app/a.py +++ b/tests/test_modules_same_name_body/app/a.py @@ -4,5 +4,5 @@ router = APIRouter() @router.post("/compute") -def compute(a: int = Body(...), b: str = Body(...)): +def compute(a: int = Body(), b: str = Body()): return {"a": a, "b": b} diff --git a/tests/test_modules_same_name_body/app/b.py b/tests/test_modules_same_name_body/app/b.py index f7c7fdfc69..b62118f841 100644 --- a/tests/test_modules_same_name_body/app/b.py +++ b/tests/test_modules_same_name_body/app/b.py @@ -4,5 +4,5 @@ router = APIRouter() @router.post("/compute/") -def compute(a: int = Body(...), b: str = Body(...)): +def compute(a: int = Body(), b: str = Body()): return {"a": a, "b": b} diff --git a/tests/test_modules_same_name_body/test_main.py b/tests/test_modules_same_name_body/test_main.py index 8b1aea0316..1ebcaee8cf 100644 --- a/tests/test_modules_same_name_body/test_main.py +++ b/tests/test_modules_same_name_body/test_main.py @@ -4,130 +4,6 @@ from .app.main import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a/compute": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Compute", - "operationId": "compute_a_compute_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_compute_a_compute_post" - } - } - }, - "required": True, - }, - } - }, - "/b/compute/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Compute", - "operationId": "compute_b_compute__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_compute_b_compute__post" - } - } - }, - "required": True, - }, - } - }, - }, - "components": { - "schemas": { - "Body_compute_b_compute__post": { - "title": "Body_compute_b_compute__post", - "required": ["a", "b"], - "type": "object", - "properties": { - "a": {"title": "A", "type": "integer"}, - "b": {"title": "B", "type": "string"}, - }, - }, - "Body_compute_a_compute_post": { - "title": "Body_compute_a_compute_post", - "required": ["a", "b"], - "type": "object", - "properties": { - "a": {"title": "A", "type": "integer"}, - "b": {"title": "B", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post_a(): data = {"a": 2, "b": "foo"} @@ -153,3 +29,127 @@ def test_post_b_invalid(): data = {"a": "bar", "b": "foo"} response = client.post("/b/compute/", json=data) assert response.status_code == 422, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/compute": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Compute", + "operationId": "compute_a_compute_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_compute_a_compute_post" + } + } + }, + "required": True, + }, + } + }, + "/b/compute/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Compute", + "operationId": "compute_b_compute__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_compute_b_compute__post" + } + } + }, + "required": True, + }, + } + }, + }, + "components": { + "schemas": { + "Body_compute_b_compute__post": { + "title": "Body_compute_b_compute__post", + "required": ["a", "b"], + "type": "object", + "properties": { + "a": {"title": "A", "type": "integer"}, + "b": {"title": "B", "type": "string"}, + }, + }, + "Body_compute_a_compute_post": { + "title": "Body_compute_a_compute_post", + "required": ["a", "b"], + "type": "object", + "properties": { + "a": {"title": "A", "type": "integer"}, + "b": {"title": "B", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py index 31308ea85c..358684bc5c 100644 --- a/tests/test_multi_body_errors.py +++ b/tests/test_multi_body_errors.py @@ -21,85 +21,6 @@ def save_item_no_body(item: List[Item]): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Save Item No Body", - "operationId": "save_item_no_body_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Item", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "age"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "age": {"title": "Age", "exclusiveMinimum": 0.0, "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - single_error = { "detail": [ { @@ -137,12 +58,6 @@ multiple_errors = { } -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_put_correct_body(): response = client.post("/items/", json=[{"name": "Foo", "age": 5}]) assert response.status_code == 200, response.text @@ -159,3 +74,92 @@ def test_put_incorrect_body_multiple(): response = client.post("/items/", json=[{"age": "five"}, {"age": "six"}]) assert response.status_code == 422, response.text assert response.json() == multiple_errors + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Save Item No Body", + "operationId": "save_item_no_body_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Item", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "age"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "age": { + "title": "Age", + "exclusiveMinimum": 0.0, + "type": "number", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py index 0a15833fa0..e7a833f2ba 100644 --- a/tests/test_multi_query_errors.py +++ b/tests/test_multi_query_errors.py @@ -7,83 +7,13 @@ app = FastAPI() @app.get("/items/") -def read_items(q: List[int] = Query(None)): +def read_items(q: List[int] = Query(default=None)): return {"q": q} client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "integer"}, - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - multiple_errors = { "detail": [ { @@ -100,12 +30,6 @@ multiple_errors = { } -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_multi_query(): response = client.get("/items/?q=5&q=6") assert response.status_code == 200, response.text @@ -116,3 +40,79 @@ def test_multi_query_incorrect(): response = client.get("/items/?q=five&q=six") assert response.status_code == 422, response.text assert response.json() == multiple_errors + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "integer"}, + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_multipart_installation.py b/tests/test_multipart_installation.py index c8a6fd942f..788d9ef5af 100644 --- a/tests/test_multipart_installation.py +++ b/tests/test_multipart_installation.py @@ -12,7 +12,7 @@ def test_incorrect_multipart_installed_form(monkeypatch): app = FastAPI() @app.post("/") - async def root(username: str = Form(...)): + async def root(username: str = Form()): return username # pragma: nocover @@ -22,7 +22,7 @@ def test_incorrect_multipart_installed_file_upload(monkeypatch): app = FastAPI() @app.post("/") - async def root(f: UploadFile = File(...)): + async def root(f: UploadFile = File()): return f # pragma: nocover @@ -32,7 +32,7 @@ def test_incorrect_multipart_installed_file_bytes(monkeypatch): app = FastAPI() @app.post("/") - async def root(f: bytes = File(...)): + async def root(f: bytes = File()): return f # pragma: nocover @@ -42,7 +42,7 @@ def test_incorrect_multipart_installed_multi_form(monkeypatch): app = FastAPI() @app.post("/") - async def root(username: str = Form(...), password: str = Form(...)): + async def root(username: str = Form(), password: str = Form()): return username # pragma: nocover @@ -52,7 +52,7 @@ def test_incorrect_multipart_installed_form_file(monkeypatch): app = FastAPI() @app.post("/") - async def root(username: str = Form(...), f: UploadFile = File(...)): + async def root(username: str = Form(), f: UploadFile = File()): return username # pragma: nocover @@ -62,7 +62,7 @@ def test_no_multipart_installed(monkeypatch): app = FastAPI() @app.post("/") - async def root(username: str = Form(...)): + async def root(username: str = Form()): return username # pragma: nocover @@ -72,7 +72,7 @@ def test_no_multipart_installed_file(monkeypatch): app = FastAPI() @app.post("/") - async def root(f: UploadFile = File(...)): + async def root(f: UploadFile = File()): return f # pragma: nocover @@ -82,7 +82,7 @@ def test_no_multipart_installed_file_bytes(monkeypatch): app = FastAPI() @app.post("/") - async def root(f: bytes = File(...)): + async def root(f: bytes = File()): return f # pragma: nocover @@ -92,7 +92,7 @@ def test_no_multipart_installed_multi_form(monkeypatch): app = FastAPI() @app.post("/") - async def root(username: str = Form(...), password: str = Form(...)): + async def root(username: str = Form(), password: str = Form()): return username # pragma: nocover @@ -102,5 +102,5 @@ def test_no_multipart_installed_form_file(monkeypatch): app = FastAPI() @app.post("/") - async def root(username: str = Form(...), f: UploadFile = File(...)): + async def root(username: str = Form(), f: UploadFile = File()): return username # pragma: nocover diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py new file mode 100644 index 0000000000..8a9086ebe1 --- /dev/null +++ b/tests/test_openapi_query_parameter_extension.py @@ -0,0 +1,126 @@ +from typing import Optional + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + + +@app.get( + "/", + openapi_extra={ + "parameters": [ + { + "required": False, + "schema": {"title": "Extra Param 1"}, + "name": "extra_param_1", + "in": "query", + }, + { + "required": True, + "schema": {"title": "Extra Param 2"}, + "name": "extra_param_2", + "in": "query", + }, + ] + }, +) +def route_with_extra_query_parameters(standard_query_param: Optional[int] = 50): + return {} + + +client = TestClient(app) + + +def test_get_route(): + response = client.get("/") + assert response.status_code == 200, response.text + assert response.json() == {} + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Route With Extra Query Parameters", + "operationId": "route_with_extra_query_parameters__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Standard Query Param", + "type": "integer", + "default": 50, + }, + "name": "standard_query_param", + "in": "query", + }, + { + "required": False, + "schema": {"title": "Extra Param 1"}, + "name": "extra_param_1", + "in": "query", + }, + { + "required": True, + "schema": {"title": "Extra Param 2"}, + "name": "extra_param_2", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_openapi_route_extensions.py b/tests/test_openapi_route_extensions.py index 8a1080d697..943dc43f2d 100644 --- a/tests/test_openapi_route_extensions.py +++ b/tests/test_openapi_route_extensions.py @@ -12,34 +12,31 @@ def route_with_extras(): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "Route With Extras", - "operationId": "route_with_extras__get", - "x-custom-extension": "value", - } - }, - }, -} +def test_get_route(): + response = client.get("/") + assert response.status_code == 200, response.text + assert response.json() == {} def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_get_route(): - response = client.get("/") - assert response.status_code == 200, response.text - assert response.json() == {} + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "Route With Extras", + "operationId": "route_with_extras__get", + "x-custom-extension": "value", + } + }, + }, + } diff --git a/tests/test_openapi_servers.py b/tests/test_openapi_servers.py index a210154f60..26abeaa12d 100644 --- a/tests/test_openapi_servers.py +++ b/tests/test_openapi_servers.py @@ -21,40 +21,37 @@ def foo(): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "servers": [ - {"url": "/", "description": "Default, relative server"}, - { - "url": "http://staging.localhost.tiangolo.com:8000", - "description": "Staging but actually localhost still", - }, - {"url": "https://prod.example.com"}, - ], - "paths": { - "/foo": { - "get": { - "summary": "Foo", - "operationId": "foo_foo_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} - - -def test_openapi_servers(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_app(): response = client.get("/foo") assert response.status_code == 200, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "servers": [ + {"url": "/", "description": "Default, relative server"}, + { + "url": "http://staging.localhost.tiangolo.com:8000", + "description": "Staging but actually localhost still", + }, + {"url": "https://prod.example.com"}, + ], + "paths": { + "/foo": { + "get": { + "summary": "Foo", + "operationId": "foo_foo_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_orjson_response_class.py b/tests/test_orjson_response_class.py new file mode 100644 index 0000000000..6fe62daf97 --- /dev/null +++ b/tests/test_orjson_response_class.py @@ -0,0 +1,21 @@ +from fastapi import FastAPI +from fastapi.responses import ORJSONResponse +from fastapi.testclient import TestClient +from sqlalchemy.sql.elements import quoted_name + +app = FastAPI(default_response_class=ORJSONResponse) + + +@app.get("/orjson_non_str_keys") +def get_orjson_non_str_keys(): + key = quoted_name(value="msg", quote=False) + return {key: "Hello World", 1: 1} + + +client = TestClient(app) + + +def test_orjson_non_str_keys(): + with client: + response = client.get("/orjson_non_str_keys") + assert response.json() == {"msg": "Hello World", "1": 1} diff --git a/tests/test_param_class.py b/tests/test_param_class.py index f5767ec96c..1fd40dcd21 100644 --- a/tests/test_param_class.py +++ b/tests/test_param_class.py @@ -8,7 +8,7 @@ app = FastAPI() @app.get("/items/") -def read_items(q: Optional[str] = Param(None)): # type: ignore +def read_items(q: Optional[str] = Param(default=None)): # type: ignore return {"q": q} diff --git a/tests/test_param_in_path_and_dependency.py b/tests/test_param_in_path_and_dependency.py index 4d85afbcee..0aef7ac7bc 100644 --- a/tests/test_param_in_path_and_dependency.py +++ b/tests/test_param_in_path_and_dependency.py @@ -15,79 +15,79 @@ async def read_users(user_id: int): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/{user_id}": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_reused_param(): - response = client.get("/openapi.json") - data = response.json() - assert data == openapi_schema - def test_read_users(): response = client.get("/users/42") assert response.status_code == 200, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + data = response.json() + assert data == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/{user_id}": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py index 26aa638971..d0c29f7b2d 100644 --- a/tests/test_param_include_in_schema.py +++ b/tests/test_param_include_in_schema.py @@ -9,33 +9,31 @@ app = FastAPI() @app.get("/hidden_cookie") async def hidden_cookie( - hidden_cookie: Optional[str] = Cookie(None, include_in_schema=False) + hidden_cookie: Optional[str] = Cookie(default=None, include_in_schema=False) ): return {"hidden_cookie": hidden_cookie} @app.get("/hidden_header") async def hidden_header( - hidden_header: Optional[str] = Header(None, include_in_schema=False) + hidden_header: Optional[str] = Header(default=None, include_in_schema=False) ): return {"hidden_header": hidden_header} @app.get("/hidden_path/{hidden_path}") -async def hidden_path(hidden_path: str = Path(..., include_in_schema=False)): +async def hidden_path(hidden_path: str = Path(include_in_schema=False)): return {"hidden_path": hidden_path} @app.get("/hidden_query") async def hidden_query( - hidden_query: Optional[str] = Query(None, include_in_schema=False) + hidden_query: Optional[str] = Query(default=None, include_in_schema=False) ): return {"hidden_query": hidden_query} -client = TestClient(app) - -openapi_shema = { +openapi_schema = { "openapi": "3.0.2", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { @@ -161,9 +159,10 @@ openapi_shema = { def test_openapi_schema(): + client = TestClient(app) response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == openapi_shema + assert response.json() == openapi_schema @pytest.mark.parametrize( @@ -184,7 +183,8 @@ def test_openapi_schema(): ], ) def test_hidden_cookie(path, cookies, expected_status, expected_response): - response = client.get(path, cookies=cookies) + client = TestClient(app, cookies=cookies) + response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response @@ -207,12 +207,14 @@ def test_hidden_cookie(path, cookies, expected_status, expected_response): ], ) def test_hidden_header(path, headers, expected_status, expected_response): + client = TestClient(app) response = client.get(path, headers=headers) assert response.status_code == expected_status assert response.json() == expected_response def test_hidden_path(): + client = TestClient(app) response = client.get("/hidden_path/hidden_path") assert response.status_code == 200 assert response.json() == {"hidden_path": "hidden_path"} @@ -234,6 +236,7 @@ def test_hidden_path(): ], ) def test_hidden_query(path, expected_status, expected_response): + client = TestClient(app) response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response diff --git a/tests/test_params_repr.py b/tests/test_params_repr.py index d721257d76..d8dca1ea42 100644 --- a/tests/test_params_repr.py +++ b/tests/test_params_repr.py @@ -19,8 +19,9 @@ def test_param_repr(params): assert repr(Param(params)) == "Param(" + str(params) + ")" -def test_path_repr(params): - assert repr(Path(params)) == "Path(Ellipsis)" +def test_path_repr(): + assert repr(Path()) == "Path(Ellipsis)" + assert repr(Path(...)) == "Path(Ellipsis)" def test_query_repr(params): diff --git a/tests/test_path.py b/tests/test_path.py index d1a58bc661..03b93623a9 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -193,7 +193,6 @@ response_less_than_equal_3 = { ("/path/bool/False", 200, False), ("/path/bool/false", 200, False), ("/path/param/foo", 200, "foo"), - ("/path/param-required/foo", 200, "foo"), ("/path/param-minlength/foo", 200, "foo"), ("/path/param-minlength/fo", 422, response_at_least_3), ("/path/param-maxlength/foo", 200, "foo"), diff --git a/tests/test_put_no_body.py b/tests/test_put_no_body.py index 3da294ccf7..a02d1152c8 100644 --- a/tests/test_put_no_body.py +++ b/tests/test_put_no_body.py @@ -12,79 +12,6 @@ def save_item_no_body(item_id: str): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Save Item No Body", - "operationId": "save_item_no_body_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_put_no_body(): response = client.put("/items/foo") assert response.status_code == 200, response.text @@ -95,3 +22,75 @@ def test_put_no_body_with_body(): response = client.put("/items/foo", json={"name": "Foo"}) assert response.status_code == 200, response.text assert response.json() == {"item_id": "foo"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Save Item No Body", + "operationId": "save_item_no_body_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_query.py b/tests/test_query.py index cdbdd1ccdc..0c73eb665e 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -53,6 +53,7 @@ response_not_valid_int = { ("/query/param-required/int", 422, response_missing), ("/query/param-required/int?query=50", 200, "foo bar 50"), ("/query/param-required/int?query=foo", 422, response_not_valid_int), + ("/query/frozenset/?query=1&query=1&query=2", 200, "1,2"), ], ) def test_get_path(path, expected_status, expected_response): diff --git a/tests/test_repeated_dependency_schema.py b/tests/test_repeated_dependency_schema.py index 00441694ee..ca0305184a 100644 --- a/tests/test_repeated_dependency_schema.py +++ b/tests/test_repeated_dependency_schema.py @@ -4,7 +4,7 @@ from fastapi.testclient import TestClient app = FastAPI() -def get_header(*, someheader: str = Header(...)): +def get_header(*, someheader: str = Header()): return someheader diff --git a/tests/test_repeated_parameter_alias.py b/tests/test_repeated_parameter_alias.py new file mode 100644 index 0000000000..c656a161df --- /dev/null +++ b/tests/test_repeated_parameter_alias.py @@ -0,0 +1,100 @@ +from fastapi import FastAPI, Path, Query, status +from fastapi.testclient import TestClient + +app = FastAPI() + + +@app.get("/{repeated_alias}") +def get_parameters_with_repeated_aliases( + path: str = Path(..., alias="repeated_alias"), + query: str = Query(..., alias="repeated_alias"), +): + return {"path": path, "query": query} + + +client = TestClient(app) + + +def test_get_parameters(): + response = client.get("/test_path", params={"repeated_alias": "test_query"}) + assert response.status_code == 200, response.text + assert response.json() == {"path": "test_path", "query": "test_query"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == status.HTTP_200_OK + actual_schema = response.json() + assert actual_schema == { + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "title": "Detail", + "type": "array", + } + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "title": "Location", + "type": "array", + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", + }, + } + }, + "info": {"title": "FastAPI", "version": "0.1.0"}, + "openapi": "3.0.2", + "paths": { + "/{repeated_alias}": { + "get": { + "operationId": "get_parameters_with_repeated_aliases__repeated_alias__get", + "parameters": [ + { + "in": "path", + "name": "repeated_alias", + "required": True, + "schema": {"title": "Repeated Alias", "type": "string"}, + }, + { + "in": "query", + "name": "repeated_alias", + "required": True, + "schema": {"title": "Repeated Alias", "type": "string"}, + }, + ], + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error", + }, + }, + "summary": "Get Parameters With Repeated Aliases", + } + } + }, + } diff --git a/tests/test_reponse_set_reponse_code_empty.py b/tests/test_reponse_set_reponse_code_empty.py new file mode 100644 index 0000000000..14770fed0a --- /dev/null +++ b/tests/test_reponse_set_reponse_code_empty.py @@ -0,0 +1,97 @@ +from typing import Any + +from fastapi import FastAPI, Response +from fastapi.testclient import TestClient + +app = FastAPI() + + +@app.delete( + "/{id}", + status_code=204, + response_model=None, +) +async def delete_deployment( + id: int, + response: Response, +) -> Any: + response.status_code = 400 + return {"msg": "Status overwritten", "id": id} + + +client = TestClient(app) + + +def test_dependency_set_status_code(): + response = client.delete("/1") + assert response.status_code == 400 and response.content + assert response.json() == {"msg": "Status overwritten", "id": 1} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/{id}": { + "delete": { + "summary": "Delete Deployment", + "operationId": "delete_deployment__id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Id", "type": "integer"}, + "name": "id", + "in": "path", + } + ], + "responses": { + "204": {"description": "Successful Response"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_request_body_parameters_media_type.py b/tests/test_request_body_parameters_media_type.py index ace6bdef76..32f6c6a725 100644 --- a/tests/test_request_body_parameters_media_type.py +++ b/tests/test_request_body_parameters_media_type.py @@ -21,48 +21,158 @@ class Shop(BaseModel): @app.post("/products") -async def create_product(data: Product = Body(..., media_type=media_type, embed=True)): +async def create_product(data: Product = Body(media_type=media_type, embed=True)): pass # pragma: no cover @app.post("/shops") async def create_shop( - data: Shop = Body(..., media_type=media_type), - included: typing.List[Product] = Body([], media_type=media_type), + data: Shop = Body(media_type=media_type), + included: typing.List[Product] = Body(default=[], media_type=media_type), ): pass # pragma: no cover -create_product_request_body = { - "content": { - "application/vnd.api+json": { - "schema": {"$ref": "#/components/schemas/Body_create_product_products_post"} - } - }, - "required": True, -} - -create_shop_request_body = { - "content": { - "application/vnd.api+json": { - "schema": {"$ref": "#/components/schemas/Body_create_shop_shops_post"} - } - }, - "required": True, -} - client = TestClient(app) def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - openapi_schema = response.json() - assert ( - openapi_schema["paths"]["/products"]["post"]["requestBody"] - == create_product_request_body - ) - assert ( - openapi_schema["paths"]["/shops"]["post"]["requestBody"] - == create_shop_request_body - ) + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/products": { + "post": { + "summary": "Create Product", + "operationId": "create_product_products_post", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/Body_create_product_products_post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/shops": { + "post": { + "summary": "Create Shop", + "operationId": "create_shop_shops_post", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/Body_create_shop_shops_post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_product_products_post": { + "title": "Body_create_product_products_post", + "required": ["data"], + "type": "object", + "properties": {"data": {"$ref": "#/components/schemas/Product"}}, + }, + "Body_create_shop_shops_post": { + "title": "Body_create_shop_shops_post", + "required": ["data"], + "type": "object", + "properties": { + "data": {"$ref": "#/components/schemas/Shop"}, + "included": { + "title": "Included", + "type": "array", + "items": {"$ref": "#/components/schemas/Product"}, + "default": [], + }, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Product": { + "title": "Product", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "Shop": { + "title": "Shop", + "required": ["name"], + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_required_noneable.py b/tests/test_required_noneable.py new file mode 100644 index 0000000000..5da8cd4d09 --- /dev/null +++ b/tests/test_required_noneable.py @@ -0,0 +1,62 @@ +from typing import Union + +from fastapi import Body, FastAPI, Query +from fastapi.testclient import TestClient + +app = FastAPI() + + +@app.get("/query") +def read_query(q: Union[str, None]): + return q + + +@app.get("/explicit-query") +def read_explicit_query(q: Union[str, None] = Query()): + return q + + +@app.post("/body-embed") +def send_body_embed(b: Union[str, None] = Body(embed=True)): + return b + + +client = TestClient(app) + + +def test_required_nonable_query_invalid(): + response = client.get("/query") + assert response.status_code == 422 + + +def test_required_noneable_query_value(): + response = client.get("/query", params={"q": "foo"}) + assert response.status_code == 200 + assert response.json() == "foo" + + +def test_required_nonable_explicit_query_invalid(): + response = client.get("/explicit-query") + assert response.status_code == 422 + + +def test_required_nonable_explicit_query_value(): + response = client.get("/explicit-query", params={"q": "foo"}) + assert response.status_code == 200 + assert response.json() == "foo" + + +def test_required_nonable_body_embed_no_content(): + response = client.post("/body-embed") + assert response.status_code == 422 + + +def test_required_nonable_body_embed_invalid(): + response = client.post("/body-embed", json={"invalid": "invalid"}) + assert response.status_code == 422 + + +def test_required_noneable_body_embed_value(): + response = client.post("/body-embed", json={"b": "foo"}) + assert response.status_code == 200 + assert response.json() == "foo" diff --git a/tests/test_response_by_alias.py b/tests/test_response_by_alias.py index de45e0880f..1861a40fab 100644 --- a/tests/test_response_by_alias.py +++ b/tests/test_response_by_alias.py @@ -68,198 +68,9 @@ def no_alias_list(): return [{"name": "Foo"}, {"name": "Bar"}] -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/dict": { - "get": { - "summary": "Read Dict", - "operationId": "read_dict_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/model": { - "get": { - "summary": "Read Model", - "operationId": "read_model_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/list": { - "get": { - "summary": "Read List", - "operationId": "read_list_list_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read List List Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Model"}, - } - } - }, - } - }, - } - }, - "/by-alias/dict": { - "get": { - "summary": "By Alias Dict", - "operationId": "by_alias_dict_by_alias_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/by-alias/model": { - "get": { - "summary": "By Alias Model", - "operationId": "by_alias_model_by_alias_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/by-alias/list": { - "get": { - "summary": "By Alias List", - "operationId": "by_alias_list_by_alias_list_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response By Alias List By Alias List Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Model"}, - } - } - }, - } - }, - } - }, - "/no-alias/dict": { - "get": { - "summary": "No Alias Dict", - "operationId": "no_alias_dict_no_alias_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ModelNoAlias"} - } - }, - } - }, - } - }, - "/no-alias/model": { - "get": { - "summary": "No Alias Model", - "operationId": "no_alias_model_no_alias_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ModelNoAlias"} - } - }, - } - }, - } - }, - "/no-alias/list": { - "get": { - "summary": "No Alias List", - "operationId": "no_alias_list_no_alias_list_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response No Alias List No Alias List Get", - "type": "array", - "items": { - "$ref": "#/components/schemas/ModelNoAlias" - }, - } - } - }, - } - }, - } - }, - }, - "components": { - "schemas": { - "Model": { - "title": "Model", - "required": ["alias"], - "type": "object", - "properties": {"alias": {"title": "Alias", "type": "string"}}, - }, - "ModelNoAlias": { - "title": "ModelNoAlias", - "required": ["name"], - "type": "object", - "properties": {"name": {"title": "Name", "type": "string"}}, - "description": "response_model_by_alias=False is basically a quick hack, to support proper OpenAPI use another model with the correct field names", - }, - } - }, -} - - client = TestClient(app) -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_read_dict(): response = client.get("/dict") assert response.status_code == 200, response.text @@ -321,3 +132,193 @@ def test_read_list_no_alias(): {"name": "Foo"}, {"name": "Bar"}, ] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/dict": { + "get": { + "summary": "Read Dict", + "operationId": "read_dict_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} + } + }, + } + }, + } + }, + "/model": { + "get": { + "summary": "Read Model", + "operationId": "read_model_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} + } + }, + } + }, + } + }, + "/list": { + "get": { + "summary": "Read List", + "operationId": "read_list_list_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read List List Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Model"}, + } + } + }, + } + }, + } + }, + "/by-alias/dict": { + "get": { + "summary": "By Alias Dict", + "operationId": "by_alias_dict_by_alias_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} + } + }, + } + }, + } + }, + "/by-alias/model": { + "get": { + "summary": "By Alias Model", + "operationId": "by_alias_model_by_alias_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} + } + }, + } + }, + } + }, + "/by-alias/list": { + "get": { + "summary": "By Alias List", + "operationId": "by_alias_list_by_alias_list_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response By Alias List By Alias List Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Model"}, + } + } + }, + } + }, + } + }, + "/no-alias/dict": { + "get": { + "summary": "No Alias Dict", + "operationId": "no_alias_dict_no_alias_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelNoAlias" + } + } + }, + } + }, + } + }, + "/no-alias/model": { + "get": { + "summary": "No Alias Model", + "operationId": "no_alias_model_no_alias_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelNoAlias" + } + } + }, + } + }, + } + }, + "/no-alias/list": { + "get": { + "summary": "No Alias List", + "operationId": "no_alias_list_no_alias_list_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Alias List No Alias List Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/ModelNoAlias" + }, + } + } + }, + } + }, + } + }, + }, + "components": { + "schemas": { + "Model": { + "title": "Model", + "required": ["alias"], + "type": "object", + "properties": {"alias": {"title": "Alias", "type": "string"}}, + }, + "ModelNoAlias": { + "title": "ModelNoAlias", + "required": ["name"], + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + "description": "response_model_by_alias=False is basically a quick hack, to support proper OpenAPI use another model with the correct field names", + }, + } + }, + } diff --git a/tests/test_response_class_no_mediatype.py b/tests/test_response_class_no_mediatype.py index eb8500f3a8..2d75c75358 100644 --- a/tests/test_response_class_no_mediatype.py +++ b/tests/test_response_class_no_mediatype.py @@ -35,80 +35,79 @@ async def b(): pass # pragma: no cover -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/JsonApiError"} - } - }, - }, - "200": {"description": "Successful Response"}, - }, - "summary": "A", - "operationId": "a_a_get", - } - }, - "/b": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Error"} - } - }, - }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "B", - "operationId": "b_b_get", - } - }, - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, -} - - client = TestClient(app) def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } + } + }, + }, + "200": {"description": "Successful Response"}, + }, + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "B", + "operationId": "b_b_get", + } + }, + }, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } + }, + }, + } + }, + } diff --git a/tests/test_response_code_no_body.py b/tests/test_response_code_no_body.py index 45e2fabc7e..3851a325de 100644 --- a/tests/test_response_code_no_body.py +++ b/tests/test_response_code_no_body.py @@ -28,7 +28,7 @@ class JsonApiError(BaseModel): responses={500: {"description": "Error", "model": JsonApiError}}, ) async def a(): - pass # pragma: no cover + pass @app.get("/b", responses={204: {"description": "No Content"}}) @@ -36,73 +36,79 @@ async def b(): pass # pragma: no cover -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/a": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/vnd.api+json": { - "schema": {"$ref": "#/components/schemas/JsonApiError"} - } - }, - }, - "204": {"description": "Successful Response"}, - }, - "summary": "A", - "operationId": "a_a_get", - } - }, - "/b": { - "get": { - "responses": { - "204": {"description": "No Content"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "B", - "operationId": "b_b_get", - } - }, - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, -} - - client = TestClient(app) +def test_get_response(): + response = client.get("/a") + assert response.status_code == 204, response.text + assert "content-length" not in response.headers + assert response.content == b"" + + def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } + } + }, + }, + "204": {"description": "Successful Response"}, + }, + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "204": {"description": "No Content"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "B", + "operationId": "b_b_get", + } + }, + }, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } + }, + }, + } + }, + } diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py new file mode 100644 index 0000000000..7decdff7d0 --- /dev/null +++ b/tests/test_response_model_as_return_annotation.py @@ -0,0 +1,1108 @@ +from typing import List, Union + +import pytest +from fastapi import FastAPI +from fastapi.exceptions import FastAPIError +from fastapi.responses import JSONResponse, Response +from fastapi.testclient import TestClient +from pydantic import BaseModel, ValidationError + + +class BaseUser(BaseModel): + name: str + + +class User(BaseUser): + surname: str + + +class DBUser(User): + password_hash: str + + +class Item(BaseModel): + name: str + price: float + + +app = FastAPI() + + +@app.get("/no_response_model-no_annotation-return_model") +def no_response_model_no_annotation_return_model(): + return User(name="John", surname="Doe") + + +@app.get("/no_response_model-no_annotation-return_dict") +def no_response_model_no_annotation_return_dict(): + return {"name": "John", "surname": "Doe"} + + +@app.get("/response_model-no_annotation-return_same_model", response_model=User) +def response_model_no_annotation_return_same_model(): + return User(name="John", surname="Doe") + + +@app.get("/response_model-no_annotation-return_exact_dict", response_model=User) +def response_model_no_annotation_return_exact_dict(): + return {"name": "John", "surname": "Doe"} + + +@app.get("/response_model-no_annotation-return_invalid_dict", response_model=User) +def response_model_no_annotation_return_invalid_dict(): + return {"name": "John"} + + +@app.get("/response_model-no_annotation-return_invalid_model", response_model=User) +def response_model_no_annotation_return_invalid_model(): + return Item(name="Foo", price=42.0) + + +@app.get( + "/response_model-no_annotation-return_dict_with_extra_data", response_model=User +) +def response_model_no_annotation_return_dict_with_extra_data(): + return {"name": "John", "surname": "Doe", "password_hash": "secret"} + + +@app.get( + "/response_model-no_annotation-return_submodel_with_extra_data", response_model=User +) +def response_model_no_annotation_return_submodel_with_extra_data(): + return DBUser(name="John", surname="Doe", password_hash="secret") + + +@app.get("/no_response_model-annotation-return_same_model") +def no_response_model_annotation_return_same_model() -> User: + return User(name="John", surname="Doe") + + +@app.get("/no_response_model-annotation-return_exact_dict") +def no_response_model_annotation_return_exact_dict() -> User: + return {"name": "John", "surname": "Doe"} + + +@app.get("/no_response_model-annotation-return_invalid_dict") +def no_response_model_annotation_return_invalid_dict() -> User: + return {"name": "John"} + + +@app.get("/no_response_model-annotation-return_invalid_model") +def no_response_model_annotation_return_invalid_model() -> User: + return Item(name="Foo", price=42.0) + + +@app.get("/no_response_model-annotation-return_dict_with_extra_data") +def no_response_model_annotation_return_dict_with_extra_data() -> User: + return {"name": "John", "surname": "Doe", "password_hash": "secret"} + + +@app.get("/no_response_model-annotation-return_submodel_with_extra_data") +def no_response_model_annotation_return_submodel_with_extra_data() -> User: + return DBUser(name="John", surname="Doe", password_hash="secret") + + +@app.get("/response_model_none-annotation-return_same_model", response_model=None) +def response_model_none_annotation_return_same_model() -> User: + return User(name="John", surname="Doe") + + +@app.get("/response_model_none-annotation-return_exact_dict", response_model=None) +def response_model_none_annotation_return_exact_dict() -> User: + return {"name": "John", "surname": "Doe"} + + +@app.get("/response_model_none-annotation-return_invalid_dict", response_model=None) +def response_model_none_annotation_return_invalid_dict() -> User: + return {"name": "John"} + + +@app.get("/response_model_none-annotation-return_invalid_model", response_model=None) +def response_model_none_annotation_return_invalid_model() -> User: + return Item(name="Foo", price=42.0) + + +@app.get( + "/response_model_none-annotation-return_dict_with_extra_data", response_model=None +) +def response_model_none_annotation_return_dict_with_extra_data() -> User: + return {"name": "John", "surname": "Doe", "password_hash": "secret"} + + +@app.get( + "/response_model_none-annotation-return_submodel_with_extra_data", + response_model=None, +) +def response_model_none_annotation_return_submodel_with_extra_data() -> User: + return DBUser(name="John", surname="Doe", password_hash="secret") + + +@app.get( + "/response_model_model1-annotation_model2-return_same_model", response_model=User +) +def response_model_model1_annotation_model2_return_same_model() -> Item: + return User(name="John", surname="Doe") + + +@app.get( + "/response_model_model1-annotation_model2-return_exact_dict", response_model=User +) +def response_model_model1_annotation_model2_return_exact_dict() -> Item: + return {"name": "John", "surname": "Doe"} + + +@app.get( + "/response_model_model1-annotation_model2-return_invalid_dict", response_model=User +) +def response_model_model1_annotation_model2_return_invalid_dict() -> Item: + return {"name": "John"} + + +@app.get( + "/response_model_model1-annotation_model2-return_invalid_model", response_model=User +) +def response_model_model1_annotation_model2_return_invalid_model() -> Item: + return Item(name="Foo", price=42.0) + + +@app.get( + "/response_model_model1-annotation_model2-return_dict_with_extra_data", + response_model=User, +) +def response_model_model1_annotation_model2_return_dict_with_extra_data() -> Item: + return {"name": "John", "surname": "Doe", "password_hash": "secret"} + + +@app.get( + "/response_model_model1-annotation_model2-return_submodel_with_extra_data", + response_model=User, +) +def response_model_model1_annotation_model2_return_submodel_with_extra_data() -> Item: + return DBUser(name="John", surname="Doe", password_hash="secret") + + +@app.get( + "/response_model_filtering_model-annotation_submodel-return_submodel", + response_model=User, +) +def response_model_filtering_model_annotation_submodel_return_submodel() -> DBUser: + return DBUser(name="John", surname="Doe", password_hash="secret") + + +@app.get("/response_model_list_of_model-no_annotation", response_model=List[User]) +def response_model_list_of_model_no_annotation(): + return [ + DBUser(name="John", surname="Doe", password_hash="secret"), + DBUser(name="Jane", surname="Does", password_hash="secret2"), + ] + + +@app.get("/no_response_model-annotation_list_of_model") +def no_response_model_annotation_list_of_model() -> List[User]: + return [ + DBUser(name="John", surname="Doe", password_hash="secret"), + DBUser(name="Jane", surname="Does", password_hash="secret2"), + ] + + +@app.get("/no_response_model-annotation_forward_ref_list_of_model") +def no_response_model_annotation_forward_ref_list_of_model() -> "List[User]": + return [ + DBUser(name="John", surname="Doe", password_hash="secret"), + DBUser(name="Jane", surname="Does", password_hash="secret2"), + ] + + +@app.get( + "/response_model_union-no_annotation-return_model1", + response_model=Union[User, Item], +) +def response_model_union_no_annotation_return_model1(): + return DBUser(name="John", surname="Doe", password_hash="secret") + + +@app.get( + "/response_model_union-no_annotation-return_model2", + response_model=Union[User, Item], +) +def response_model_union_no_annotation_return_model2(): + return Item(name="Foo", price=42.0) + + +@app.get("/no_response_model-annotation_union-return_model1") +def no_response_model_annotation_union_return_model1() -> Union[User, Item]: + return DBUser(name="John", surname="Doe", password_hash="secret") + + +@app.get("/no_response_model-annotation_union-return_model2") +def no_response_model_annotation_union_return_model2() -> Union[User, Item]: + return Item(name="Foo", price=42.0) + + +@app.get("/no_response_model-annotation_response_class") +def no_response_model_annotation_response_class() -> Response: + return Response(content="Foo") + + +@app.get("/no_response_model-annotation_json_response_class") +def no_response_model_annotation_json_response_class() -> JSONResponse: + return JSONResponse(content={"foo": "bar"}) + + +client = TestClient(app) + + +def test_no_response_model_no_annotation_return_model(): + response = client.get("/no_response_model-no_annotation-return_model") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_no_response_model_no_annotation_return_dict(): + response = client.get("/no_response_model-no_annotation-return_dict") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_no_annotation_return_same_model(): + response = client.get("/response_model-no_annotation-return_same_model") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_no_annotation_return_exact_dict(): + response = client.get("/response_model-no_annotation-return_exact_dict") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_no_annotation_return_invalid_dict(): + with pytest.raises(ValidationError): + client.get("/response_model-no_annotation-return_invalid_dict") + + +def test_response_model_no_annotation_return_invalid_model(): + with pytest.raises(ValidationError): + client.get("/response_model-no_annotation-return_invalid_model") + + +def test_response_model_no_annotation_return_dict_with_extra_data(): + response = client.get("/response_model-no_annotation-return_dict_with_extra_data") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_no_annotation_return_submodel_with_extra_data(): + response = client.get( + "/response_model-no_annotation-return_submodel_with_extra_data" + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_no_response_model_annotation_return_same_model(): + response = client.get("/no_response_model-annotation-return_same_model") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_no_response_model_annotation_return_exact_dict(): + response = client.get("/no_response_model-annotation-return_exact_dict") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_no_response_model_annotation_return_invalid_dict(): + with pytest.raises(ValidationError): + client.get("/no_response_model-annotation-return_invalid_dict") + + +def test_no_response_model_annotation_return_invalid_model(): + with pytest.raises(ValidationError): + client.get("/no_response_model-annotation-return_invalid_model") + + +def test_no_response_model_annotation_return_dict_with_extra_data(): + response = client.get("/no_response_model-annotation-return_dict_with_extra_data") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_no_response_model_annotation_return_submodel_with_extra_data(): + response = client.get( + "/no_response_model-annotation-return_submodel_with_extra_data" + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_none_annotation_return_same_model(): + response = client.get("/response_model_none-annotation-return_same_model") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_none_annotation_return_exact_dict(): + response = client.get("/response_model_none-annotation-return_exact_dict") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_none_annotation_return_invalid_dict(): + response = client.get("/response_model_none-annotation-return_invalid_dict") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John"} + + +def test_response_model_none_annotation_return_invalid_model(): + response = client.get("/response_model_none-annotation-return_invalid_model") + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "price": 42.0} + + +def test_response_model_none_annotation_return_dict_with_extra_data(): + response = client.get("/response_model_none-annotation-return_dict_with_extra_data") + assert response.status_code == 200, response.text + assert response.json() == { + "name": "John", + "surname": "Doe", + "password_hash": "secret", + } + + +def test_response_model_none_annotation_return_submodel_with_extra_data(): + response = client.get( + "/response_model_none-annotation-return_submodel_with_extra_data" + ) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "John", + "surname": "Doe", + "password_hash": "secret", + } + + +def test_response_model_model1_annotation_model2_return_same_model(): + response = client.get("/response_model_model1-annotation_model2-return_same_model") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_model1_annotation_model2_return_exact_dict(): + response = client.get("/response_model_model1-annotation_model2-return_exact_dict") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_model1_annotation_model2_return_invalid_dict(): + with pytest.raises(ValidationError): + client.get("/response_model_model1-annotation_model2-return_invalid_dict") + + +def test_response_model_model1_annotation_model2_return_invalid_model(): + with pytest.raises(ValidationError): + client.get("/response_model_model1-annotation_model2-return_invalid_model") + + +def test_response_model_model1_annotation_model2_return_dict_with_extra_data(): + response = client.get( + "/response_model_model1-annotation_model2-return_dict_with_extra_data" + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_model1_annotation_model2_return_submodel_with_extra_data(): + response = client.get( + "/response_model_model1-annotation_model2-return_submodel_with_extra_data" + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_filtering_model_annotation_submodel_return_submodel(): + response = client.get( + "/response_model_filtering_model-annotation_submodel-return_submodel" + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_list_of_model_no_annotation(): + response = client.get("/response_model_list_of_model-no_annotation") + assert response.status_code == 200, response.text + assert response.json() == [ + {"name": "John", "surname": "Doe"}, + {"name": "Jane", "surname": "Does"}, + ] + + +def test_no_response_model_annotation_list_of_model(): + response = client.get("/no_response_model-annotation_list_of_model") + assert response.status_code == 200, response.text + assert response.json() == [ + {"name": "John", "surname": "Doe"}, + {"name": "Jane", "surname": "Does"}, + ] + + +def test_no_response_model_annotation_forward_ref_list_of_model(): + response = client.get("/no_response_model-annotation_forward_ref_list_of_model") + assert response.status_code == 200, response.text + assert response.json() == [ + {"name": "John", "surname": "Doe"}, + {"name": "Jane", "surname": "Does"}, + ] + + +def test_response_model_union_no_annotation_return_model1(): + response = client.get("/response_model_union-no_annotation-return_model1") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_response_model_union_no_annotation_return_model2(): + response = client.get("/response_model_union-no_annotation-return_model2") + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "price": 42.0} + + +def test_no_response_model_annotation_union_return_model1(): + response = client.get("/no_response_model-annotation_union-return_model1") + assert response.status_code == 200, response.text + assert response.json() == {"name": "John", "surname": "Doe"} + + +def test_no_response_model_annotation_union_return_model2(): + response = client.get("/no_response_model-annotation_union-return_model2") + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "price": 42.0} + + +def test_no_response_model_annotation_return_class(): + response = client.get("/no_response_model-annotation_response_class") + assert response.status_code == 200, response.text + assert response.text == "Foo" + + +def test_no_response_model_annotation_json_response_class(): + response = client.get("/no_response_model-annotation_json_response_class") + assert response.status_code == 200, response.text + assert response.json() == {"foo": "bar"} + + +def test_invalid_response_model_field(): + app = FastAPI() + with pytest.raises(FastAPIError) as e: + + @app.get("/") + def read_root() -> Union[Response, None]: + return Response(content="Foo") # pragma: no cover + + assert "valid Pydantic field type" in e.value.args[0] + assert "parameter response_model=None" in e.value.args[0] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/no_response_model-no_annotation-return_model": { + "get": { + "summary": "No Response Model No Annotation Return Model", + "operationId": "no_response_model_no_annotation_return_model_no_response_model_no_annotation_return_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/no_response_model-no_annotation-return_dict": { + "get": { + "summary": "No Response Model No Annotation Return Dict", + "operationId": "no_response_model_no_annotation_return_dict_no_response_model_no_annotation_return_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model-no_annotation-return_same_model": { + "get": { + "summary": "Response Model No Annotation Return Same Model", + "operationId": "response_model_no_annotation_return_same_model_response_model_no_annotation_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_exact_dict": { + "get": { + "summary": "Response Model No Annotation Return Exact Dict", + "operationId": "response_model_no_annotation_return_exact_dict_response_model_no_annotation_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_invalid_dict": { + "get": { + "summary": "Response Model No Annotation Return Invalid Dict", + "operationId": "response_model_no_annotation_return_invalid_dict_response_model_no_annotation_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_invalid_model": { + "get": { + "summary": "Response Model No Annotation Return Invalid Model", + "operationId": "response_model_no_annotation_return_invalid_model_response_model_no_annotation_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_dict_with_extra_data": { + "get": { + "summary": "Response Model No Annotation Return Dict With Extra Data", + "operationId": "response_model_no_annotation_return_dict_with_extra_data_response_model_no_annotation_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_submodel_with_extra_data": { + "get": { + "summary": "Response Model No Annotation Return Submodel With Extra Data", + "operationId": "response_model_no_annotation_return_submodel_with_extra_data_response_model_no_annotation_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_same_model": { + "get": { + "summary": "No Response Model Annotation Return Same Model", + "operationId": "no_response_model_annotation_return_same_model_no_response_model_annotation_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_exact_dict": { + "get": { + "summary": "No Response Model Annotation Return Exact Dict", + "operationId": "no_response_model_annotation_return_exact_dict_no_response_model_annotation_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_invalid_dict": { + "get": { + "summary": "No Response Model Annotation Return Invalid Dict", + "operationId": "no_response_model_annotation_return_invalid_dict_no_response_model_annotation_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_invalid_model": { + "get": { + "summary": "No Response Model Annotation Return Invalid Model", + "operationId": "no_response_model_annotation_return_invalid_model_no_response_model_annotation_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_dict_with_extra_data": { + "get": { + "summary": "No Response Model Annotation Return Dict With Extra Data", + "operationId": "no_response_model_annotation_return_dict_with_extra_data_no_response_model_annotation_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_submodel_with_extra_data": { + "get": { + "summary": "No Response Model Annotation Return Submodel With Extra Data", + "operationId": "no_response_model_annotation_return_submodel_with_extra_data_no_response_model_annotation_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_none-annotation-return_same_model": { + "get": { + "summary": "Response Model None Annotation Return Same Model", + "operationId": "response_model_none_annotation_return_same_model_response_model_none_annotation_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_exact_dict": { + "get": { + "summary": "Response Model None Annotation Return Exact Dict", + "operationId": "response_model_none_annotation_return_exact_dict_response_model_none_annotation_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_invalid_dict": { + "get": { + "summary": "Response Model None Annotation Return Invalid Dict", + "operationId": "response_model_none_annotation_return_invalid_dict_response_model_none_annotation_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_invalid_model": { + "get": { + "summary": "Response Model None Annotation Return Invalid Model", + "operationId": "response_model_none_annotation_return_invalid_model_response_model_none_annotation_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_dict_with_extra_data": { + "get": { + "summary": "Response Model None Annotation Return Dict With Extra Data", + "operationId": "response_model_none_annotation_return_dict_with_extra_data_response_model_none_annotation_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_submodel_with_extra_data": { + "get": { + "summary": "Response Model None Annotation Return Submodel With Extra Data", + "operationId": "response_model_none_annotation_return_submodel_with_extra_data_response_model_none_annotation_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_same_model": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Same Model", + "operationId": "response_model_model1_annotation_model2_return_same_model_response_model_model1_annotation_model2_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_exact_dict": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Exact Dict", + "operationId": "response_model_model1_annotation_model2_return_exact_dict_response_model_model1_annotation_model2_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_invalid_dict": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Invalid Dict", + "operationId": "response_model_model1_annotation_model2_return_invalid_dict_response_model_model1_annotation_model2_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_invalid_model": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Invalid Model", + "operationId": "response_model_model1_annotation_model2_return_invalid_model_response_model_model1_annotation_model2_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_dict_with_extra_data": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Dict With Extra Data", + "operationId": "response_model_model1_annotation_model2_return_dict_with_extra_data_response_model_model1_annotation_model2_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_submodel_with_extra_data": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Submodel With Extra Data", + "operationId": "response_model_model1_annotation_model2_return_submodel_with_extra_data_response_model_model1_annotation_model2_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_filtering_model-annotation_submodel-return_submodel": { + "get": { + "summary": "Response Model Filtering Model Annotation Submodel Return Submodel", + "operationId": "response_model_filtering_model_annotation_submodel_return_submodel_response_model_filtering_model_annotation_submodel_return_submodel_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_list_of_model-no_annotation": { + "get": { + "summary": "Response Model List Of Model No Annotation", + "operationId": "response_model_list_of_model_no_annotation_response_model_list_of_model_no_annotation_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Response Model List Of Model No Annotation Response Model List Of Model No Annotation Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_list_of_model": { + "get": { + "summary": "No Response Model Annotation List Of Model", + "operationId": "no_response_model_annotation_list_of_model_no_response_model_annotation_list_of_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation List Of Model No Response Model Annotation List Of Model Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_forward_ref_list_of_model": { + "get": { + "summary": "No Response Model Annotation Forward Ref List Of Model", + "operationId": "no_response_model_annotation_forward_ref_list_of_model_no_response_model_annotation_forward_ref_list_of_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation Forward Ref List Of Model No Response Model Annotation Forward Ref List Of Model Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + } + }, + } + }, + "/response_model_union-no_annotation-return_model1": { + "get": { + "summary": "Response Model Union No Annotation Return Model1", + "operationId": "response_model_union_no_annotation_return_model1_response_model_union_no_annotation_return_model1_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Response Model Union No Annotation Return Model1 Response Model Union No Annotation Return Model1 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/response_model_union-no_annotation-return_model2": { + "get": { + "summary": "Response Model Union No Annotation Return Model2", + "operationId": "response_model_union_no_annotation_return_model2_response_model_union_no_annotation_return_model2_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Response Model Union No Annotation Return Model2 Response Model Union No Annotation Return Model2 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_union-return_model1": { + "get": { + "summary": "No Response Model Annotation Union Return Model1", + "operationId": "no_response_model_annotation_union_return_model1_no_response_model_annotation_union_return_model1_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation Union Return Model1 No Response Model Annotation Union Return Model1 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_union-return_model2": { + "get": { + "summary": "No Response Model Annotation Union Return Model2", + "operationId": "no_response_model_annotation_union_return_model2_no_response_model_annotation_union_return_model2_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation Union Return Model2 No Response Model Annotation Union Return Model2 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_response_class": { + "get": { + "summary": "No Response Model Annotation Response Class", + "operationId": "no_response_model_annotation_response_class_no_response_model_annotation_response_class_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/no_response_model-annotation_json_response_class": { + "get": { + "summary": "No Response Model Annotation Json Response Class", + "operationId": "no_response_model_annotation_json_response_class_no_response_model_annotation_json_response_class_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "User": { + "title": "User", + "required": ["name", "surname"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "surname": {"title": "Surname", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_response_model_sub_types.py b/tests/test_response_model_sub_types.py index fd972e6a3d..e462006ff1 100644 --- a/tests/test_response_model_sub_types.py +++ b/tests/test_response_model_sub_types.py @@ -32,123 +32,9 @@ def valid4(): pass -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/valid1": { - "get": { - "summary": "Valid1", - "operationId": "valid1_valid1_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "title": "Response 500 Valid1 Valid1 Get", - "type": "integer", - } - } - }, - }, - }, - } - }, - "/valid2": { - "get": { - "summary": "Valid2", - "operationId": "valid2_valid2_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "title": "Response 500 Valid2 Valid2 Get", - "type": "array", - "items": {"type": "integer"}, - } - } - }, - }, - }, - } - }, - "/valid3": { - "get": { - "summary": "Valid3", - "operationId": "valid3_valid3_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - }, - }, - } - }, - "/valid4": { - "get": { - "summary": "Valid4", - "operationId": "valid4_valid4_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "title": "Response 500 Valid4 Valid4 Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Model"}, - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Model": { - "title": "Model", - "required": ["name"], - "type": "object", - "properties": {"name": {"title": "Name", "type": "string"}}, - } - } - }, -} - client = TestClient(app) -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_path_operations(): response = client.get("/valid1") assert response.status_code == 200, response.text @@ -158,3 +44,115 @@ def test_path_operations(): assert response.status_code == 200, response.text response = client.get("/valid4") assert response.status_code == 200, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/valid1": { + "get": { + "summary": "Valid1", + "operationId": "valid1_valid1_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "title": "Response 500 Valid1 Valid1 Get", + "type": "integer", + } + } + }, + }, + }, + } + }, + "/valid2": { + "get": { + "summary": "Valid2", + "operationId": "valid2_valid2_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "title": "Response 500 Valid2 Valid2 Get", + "type": "array", + "items": {"type": "integer"}, + } + } + }, + }, + }, + } + }, + "/valid3": { + "get": { + "summary": "Valid3", + "operationId": "valid3_valid3_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} + } + }, + }, + }, + } + }, + "/valid4": { + "get": { + "summary": "Valid4", + "operationId": "valid4_valid4_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "title": "Response 500 Valid4 Valid4 Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Model"}, + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Model": { + "title": "Model", + "required": ["name"], + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + } + } + }, + } diff --git a/tests/test_route_scope.py b/tests/test_route_scope.py index a188e9a5fe..2021c828f4 100644 --- a/tests/test_route_scope.py +++ b/tests/test_route_scope.py @@ -46,5 +46,5 @@ def test_websocket(): def test_websocket_invalid_path_doesnt_match(): with pytest.raises(WebSocketDisconnect): - with client.websocket_connect("/itemsx/portal-gun") as websocket: - websocket.receive_json() + with client.websocket_connect("/itemsx/portal-gun"): + pass diff --git a/tests/test_router_events.py b/tests/test_router_events.py index 5ff1fdf9f2..ba6b763828 100644 --- a/tests/test_router_events.py +++ b/tests/test_router_events.py @@ -1,3 +1,7 @@ +from contextlib import asynccontextmanager +from typing import AsyncGenerator, Dict + +import pytest from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -12,57 +16,49 @@ class State(BaseModel): sub_router_shutdown: bool = False -state = State() - -app = FastAPI() +@pytest.fixture +def state() -> State: + return State() -@app.on_event("startup") -def app_startup(): - state.app_startup = True +def test_router_events(state: State) -> None: + app = FastAPI() + @app.get("/") + def main() -> Dict[str, str]: + return {"message": "Hello World"} -@app.on_event("shutdown") -def app_shutdown(): - state.app_shutdown = True + @app.on_event("startup") + def app_startup() -> None: + state.app_startup = True + @app.on_event("shutdown") + def app_shutdown() -> None: + state.app_shutdown = True -router = APIRouter() + router = APIRouter() + @router.on_event("startup") + def router_startup() -> None: + state.router_startup = True -@router.on_event("startup") -def router_startup(): - state.router_startup = True + @router.on_event("shutdown") + def router_shutdown() -> None: + state.router_shutdown = True + sub_router = APIRouter() -@router.on_event("shutdown") -def router_shutdown(): - state.router_shutdown = True + @sub_router.on_event("startup") + def sub_router_startup() -> None: + state.sub_router_startup = True + @sub_router.on_event("shutdown") + def sub_router_shutdown() -> None: + state.sub_router_shutdown = True -sub_router = APIRouter() + router.include_router(sub_router) + app.include_router(router) - -@sub_router.on_event("startup") -def sub_router_startup(): - state.sub_router_startup = True - - -@sub_router.on_event("shutdown") -def sub_router_shutdown(): - state.sub_router_shutdown = True - - -@sub_router.get("/") -def main(): - return {"message": "Hello World"} - - -router.include_router(sub_router) -app.include_router(router) - - -def test_router_events(): assert state.app_startup is False assert state.router_startup is False assert state.sub_router_startup is False @@ -85,3 +81,28 @@ def test_router_events(): assert state.app_shutdown is True assert state.router_shutdown is True assert state.sub_router_shutdown is True + + +def test_app_lifespan_state(state: State) -> None: + @asynccontextmanager + async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: + state.app_startup = True + yield + state.app_shutdown = True + + app = FastAPI(lifespan=lifespan) + + @app.get("/") + def main() -> Dict[str, str]: + return {"message": "Hello World"} + + assert state.app_startup is False + assert state.app_shutdown is False + with TestClient(app) as client: + assert state.app_startup is True + assert state.app_shutdown is False + response = client.get("/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello World"} + assert state.app_startup is True + assert state.app_shutdown is True diff --git a/tests/test_router_redirect_slashes.py b/tests/test_router_redirect_slashes.py new file mode 100644 index 0000000000..086665c040 --- /dev/null +++ b/tests/test_router_redirect_slashes.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient + + +def test_redirect_slashes_enabled(): + app = FastAPI() + router = APIRouter() + + @router.get("/hello/") + def hello_page() -> str: + return "Hello, World!" + + app.include_router(router) + + client = TestClient(app) + + response = client.get("/hello/", follow_redirects=False) + assert response.status_code == 200 + + response = client.get("/hello", follow_redirects=False) + assert response.status_code == 307 + + +def test_redirect_slashes_disabled(): + app = FastAPI(redirect_slashes=False) + router = APIRouter() + + @router.get("/hello/") + def hello_page() -> str: + return "Hello, World!" + + app.include_router(router) + + client = TestClient(app) + + response = client.get("/hello/", follow_redirects=False) + assert response.status_code == 200 + + response = client.get("/hello", follow_redirects=False) + assert response.status_code == 404 diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index 444e350a86..41021a9839 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -1,3 +1,5 @@ +from typing import Union + from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient from pydantic import BaseModel @@ -18,14 +20,13 @@ def schema_extra(item: Item): @app.post("/example/") -def example(item: Item = Body(..., example={"data": "Data in Body example"})): +def example(item: Item = Body(example={"data": "Data in Body example"})): return item @app.post("/examples/") def examples( item: Item = Body( - ..., examples={ "example1": { "summary": "example1 summary", @@ -41,8 +42,7 @@ def examples( @app.post("/example_examples/") def example_examples( item: Item = Body( - ..., - example={"data": "Overriden example"}, + example={"data": "Overridden example"}, examples={ "example1": {"value": {"data": "examples example_examples 1"}}, "example2": {"value": {"data": "examples example_examples 2"}}, @@ -55,7 +55,7 @@ def example_examples( # TODO: enable these tests once/if Form(embed=False) is supported # TODO: In that case, define if File() should support example/examples too # @app.post("/form_example") -# def form_example(firstname: str = Form(..., example="John")): +# def form_example(firstname: str = Form(example="John")): # return firstname @@ -76,7 +76,7 @@ def example_examples( # def form_example_examples( # lastname: str = Form( # ..., -# example="Doe overriden", +# example="Doe overridden", # examples={ # "example1": {"summary": "last name summary", "value": "Doe"}, # "example2": {"value": "Doesn't"}, @@ -89,7 +89,6 @@ def example_examples( @app.get("/path_example/{item_id}") def path_example( item_id: str = Path( - ..., example="item_1", ), ): @@ -99,7 +98,6 @@ def path_example( @app.get("/path_examples/{item_id}") def path_examples( item_id: str = Path( - ..., examples={ "example1": {"summary": "item ID summary", "value": "item_1"}, "example2": {"value": "item_2"}, @@ -112,8 +110,7 @@ def path_examples( @app.get("/path_example_examples/{item_id}") def path_example_examples( item_id: str = Path( - ..., - example="item_overriden", + example="item_overridden", examples={ "example1": {"summary": "item ID summary", "value": "item_1"}, "example2": {"value": "item_2"}, @@ -125,8 +122,8 @@ def path_example_examples( @app.get("/query_example/") def query_example( - data: str = Query( - None, + data: Union[str, None] = Query( + default=None, example="query1", ), ): @@ -135,8 +132,8 @@ def query_example( @app.get("/query_examples/") def query_examples( - data: str = Query( - None, + data: Union[str, None] = Query( + default=None, examples={ "example1": {"summary": "Query example 1", "value": "query1"}, "example2": {"value": "query2"}, @@ -148,11 +145,11 @@ def query_examples( @app.get("/query_example_examples/") def query_example_examples( - data: str = Query( - None, - example="query_overriden", + data: Union[str, None] = Query( + default=None, + example="query_overridden", examples={ - "example1": {"summary": "Qeury example 1", "value": "query1"}, + "example1": {"summary": "Query example 1", "value": "query1"}, "example2": {"value": "query2"}, }, ), @@ -162,8 +159,8 @@ def query_example_examples( @app.get("/header_example/") def header_example( - data: str = Header( - None, + data: Union[str, None] = Header( + default=None, example="header1", ), ): @@ -172,8 +169,8 @@ def header_example( @app.get("/header_examples/") def header_examples( - data: str = Header( - None, + data: Union[str, None] = Header( + default=None, examples={ "example1": {"summary": "header example 1", "value": "header1"}, "example2": {"value": "header2"}, @@ -185,11 +182,11 @@ def header_examples( @app.get("/header_example_examples/") def header_example_examples( - data: str = Header( - None, - example="header_overriden", + data: Union[str, None] = Header( + default=None, + example="header_overridden", examples={ - "example1": {"summary": "Qeury example 1", "value": "header1"}, + "example1": {"summary": "Query example 1", "value": "header1"}, "example2": {"value": "header2"}, }, ), @@ -199,8 +196,8 @@ def header_example_examples( @app.get("/cookie_example/") def cookie_example( - data: str = Cookie( - None, + data: Union[str, None] = Cookie( + default=None, example="cookie1", ), ): @@ -209,8 +206,8 @@ def cookie_example( @app.get("/cookie_examples/") def cookie_examples( - data: str = Cookie( - None, + data: Union[str, None] = Cookie( + default=None, examples={ "example1": {"summary": "cookie example 1", "value": "cookie1"}, "example2": {"value": "cookie2"}, @@ -222,11 +219,11 @@ def cookie_examples( @app.get("/cookie_example_examples/") def cookie_example_examples( - data: str = Cookie( - None, - example="cookie_overriden", + data: Union[str, None] = Cookie( + default=None, + example="cookie_overridden", examples={ - "example1": {"summary": "Qeury example 1", "value": "cookie1"}, + "example1": {"summary": "Query example 1", "value": "cookie1"}, "example2": {"value": "cookie2"}, }, ), @@ -237,623 +234,6 @@ def cookie_example_examples( client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/schema_extra/": { - "post": { - "summary": "Schema Extra", - "operationId": "schema_extra_schema_extra__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/example/": { - "post": { - "summary": "Example", - "operationId": "example_example__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "example": {"data": "Data in Body example"}, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/examples/": { - "post": { - "summary": "Examples", - "operationId": "examples_examples__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "example1": { - "summary": "example1 summary", - "value": { - "data": "Data in Body examples, example1" - }, - }, - "example2": { - "value": {"data": "Data in Body examples, example2"} - }, - }, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/example_examples/": { - "post": { - "summary": "Example Examples", - "operationId": "example_examples_example_examples__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "example1": { - "value": {"data": "examples example_examples 1"} - }, - "example2": { - "value": {"data": "examples example_examples 2"} - }, - }, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/path_example/{item_id}": { - "get": { - "summary": "Path Example", - "operationId": "path_example_path_example__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "example": "item_1", - "name": "item_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/path_examples/{item_id}": { - "get": { - "summary": "Path Examples", - "operationId": "path_examples_path_examples__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "examples": { - "example1": { - "summary": "item ID summary", - "value": "item_1", - }, - "example2": {"value": "item_2"}, - }, - "name": "item_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/path_example_examples/{item_id}": { - "get": { - "summary": "Path Example Examples", - "operationId": "path_example_examples_path_example_examples__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "examples": { - "example1": { - "summary": "item ID summary", - "value": "item_1", - }, - "example2": {"value": "item_2"}, - }, - "name": "item_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query_example/": { - "get": { - "summary": "Query Example", - "operationId": "query_example_query_example__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "example": "query1", - "name": "data", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query_examples/": { - "get": { - "summary": "Query Examples", - "operationId": "query_examples_query_examples__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "examples": { - "example1": { - "summary": "Query example 1", - "value": "query1", - }, - "example2": {"value": "query2"}, - }, - "name": "data", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query_example_examples/": { - "get": { - "summary": "Query Example Examples", - "operationId": "query_example_examples_query_example_examples__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "examples": { - "example1": { - "summary": "Qeury example 1", - "value": "query1", - }, - "example2": {"value": "query2"}, - }, - "name": "data", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/header_example/": { - "get": { - "summary": "Header Example", - "operationId": "header_example_header_example__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "example": "header1", - "name": "data", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/header_examples/": { - "get": { - "summary": "Header Examples", - "operationId": "header_examples_header_examples__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "examples": { - "example1": { - "summary": "header example 1", - "value": "header1", - }, - "example2": {"value": "header2"}, - }, - "name": "data", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/header_example_examples/": { - "get": { - "summary": "Header Example Examples", - "operationId": "header_example_examples_header_example_examples__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "examples": { - "example1": { - "summary": "Qeury example 1", - "value": "header1", - }, - "example2": {"value": "header2"}, - }, - "name": "data", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookie_example/": { - "get": { - "summary": "Cookie Example", - "operationId": "cookie_example_cookie_example__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "example": "cookie1", - "name": "data", - "in": "cookie", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookie_examples/": { - "get": { - "summary": "Cookie Examples", - "operationId": "cookie_examples_cookie_examples__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "examples": { - "example1": { - "summary": "cookie example 1", - "value": "cookie1", - }, - "example2": {"value": "cookie2"}, - }, - "name": "data", - "in": "cookie", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookie_example_examples/": { - "get": { - "summary": "Cookie Example Examples", - "operationId": "cookie_example_examples_cookie_example_examples__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Data", "type": "string"}, - "examples": { - "example1": { - "summary": "Qeury example 1", - "value": "cookie1", - }, - "example2": {"value": "cookie2"}, - }, - "name": "data", - "in": "cookie", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["data"], - "type": "object", - "properties": {"data": {"title": "Data", "type": "string"}}, - "example": {"data": "Data in schema_extra"}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - """ - Test that example overrides work: - - * pydantic model schema_extra is included - * Body(example={}) overrides schema_extra in pydantic model - * Body(examples{}) overrides Body(example={}) and schema_extra in pydantic model - """ - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_call_api(): response = client.post("/schema_extra/", json={"data": "Foo"}) assert response.status_code == 200, response.text @@ -887,3 +267,621 @@ def test_call_api(): assert response.status_code == 200, response.text response = client.get("/cookie_example_examples/") assert response.status_code == 200, response.text + + +def test_openapi_schema(): + """ + Test that example overrides work: + + * pydantic model schema_extra is included + * Body(example={}) overrides schema_extra in pydantic model + * Body(examples{}) overrides Body(example={}) and schema_extra in pydantic model + """ + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/schema_extra/": { + "post": { + "summary": "Schema Extra", + "operationId": "schema_extra_schema_extra__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/example/": { + "post": { + "summary": "Example", + "operationId": "example_example__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "example": {"data": "Data in Body example"}, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/examples/": { + "post": { + "summary": "Examples", + "operationId": "examples_examples__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "examples": { + "example1": { + "summary": "example1 summary", + "value": { + "data": "Data in Body examples, example1" + }, + }, + "example2": { + "value": { + "data": "Data in Body examples, example2" + } + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/example_examples/": { + "post": { + "summary": "Example Examples", + "operationId": "example_examples_example_examples__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "examples": { + "example1": { + "value": {"data": "examples example_examples 1"} + }, + "example2": { + "value": {"data": "examples example_examples 2"} + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/path_example/{item_id}": { + "get": { + "summary": "Path Example", + "operationId": "path_example_path_example__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "example": "item_1", + "name": "item_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/path_examples/{item_id}": { + "get": { + "summary": "Path Examples", + "operationId": "path_examples_path_examples__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "examples": { + "example1": { + "summary": "item ID summary", + "value": "item_1", + }, + "example2": {"value": "item_2"}, + }, + "name": "item_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/path_example_examples/{item_id}": { + "get": { + "summary": "Path Example Examples", + "operationId": "path_example_examples_path_example_examples__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "examples": { + "example1": { + "summary": "item ID summary", + "value": "item_1", + }, + "example2": {"value": "item_2"}, + }, + "name": "item_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query_example/": { + "get": { + "summary": "Query Example", + "operationId": "query_example_query_example__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "example": "query1", + "name": "data", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query_examples/": { + "get": { + "summary": "Query Examples", + "operationId": "query_examples_query_examples__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "examples": { + "example1": { + "summary": "Query example 1", + "value": "query1", + }, + "example2": {"value": "query2"}, + }, + "name": "data", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query_example_examples/": { + "get": { + "summary": "Query Example Examples", + "operationId": "query_example_examples_query_example_examples__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "examples": { + "example1": { + "summary": "Query example 1", + "value": "query1", + }, + "example2": {"value": "query2"}, + }, + "name": "data", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/header_example/": { + "get": { + "summary": "Header Example", + "operationId": "header_example_header_example__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "example": "header1", + "name": "data", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/header_examples/": { + "get": { + "summary": "Header Examples", + "operationId": "header_examples_header_examples__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "examples": { + "example1": { + "summary": "header example 1", + "value": "header1", + }, + "example2": {"value": "header2"}, + }, + "name": "data", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/header_example_examples/": { + "get": { + "summary": "Header Example Examples", + "operationId": "header_example_examples_header_example_examples__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "examples": { + "example1": { + "summary": "Query example 1", + "value": "header1", + }, + "example2": {"value": "header2"}, + }, + "name": "data", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_example/": { + "get": { + "summary": "Cookie Example", + "operationId": "cookie_example_cookie_example__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "example": "cookie1", + "name": "data", + "in": "cookie", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_examples/": { + "get": { + "summary": "Cookie Examples", + "operationId": "cookie_examples_cookie_examples__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "examples": { + "example1": { + "summary": "cookie example 1", + "value": "cookie1", + }, + "example2": {"value": "cookie2"}, + }, + "name": "data", + "in": "cookie", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_example_examples/": { + "get": { + "summary": "Cookie Example Examples", + "operationId": "cookie_example_examples_cookie_example_examples__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Data", "type": "string"}, + "examples": { + "example1": { + "summary": "Query example 1", + "value": "cookie1", + }, + "example2": {"value": "cookie2"}, + }, + "name": "data", + "in": "cookie", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["data"], + "type": "object", + "properties": {"data": {"title": "Data", "type": "string"}}, + "example": {"data": "Data in schema_extra"}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_security_api_key_cookie.py b/tests/test_security_api_key_cookie.py index a5b2e44f0c..b1c648c55e 100644 --- a/tests/test_security_api_key_cookie.py +++ b/tests/test_security_api_key_cookie.py @@ -22,47 +22,45 @@ def read_current_user(current_user: User = Depends(get_current_user)): return current_user -client = TestClient(app) - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyCookie": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_security_api_key(): - response = client.get("/users/me", cookies={"key": "secret"}) + client = TestClient(app, cookies={"key": "secret"}) + response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"username": "secret"} def test_security_api_key_no_key(): + client = TestClient(app) response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyCookie": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} + } + }, + } diff --git a/tests/test_security_api_key_cookie_description.py b/tests/test_security_api_key_cookie_description.py index 2cd3565b43..ac8b4abf87 100644 --- a/tests/test_security_api_key_cookie_description.py +++ b/tests/test_security_api_key_cookie_description.py @@ -22,52 +22,50 @@ def read_current_user(current_user: User = Depends(get_current_user)): return current_user -client = TestClient(app) - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyCookie": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyCookie": { - "type": "apiKey", - "name": "key", - "in": "cookie", - "description": "An API Cookie Key", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_security_api_key(): - response = client.get("/users/me", cookies={"key": "secret"}) + client = TestClient(app, cookies={"key": "secret"}) + response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"username": "secret"} def test_security_api_key_no_key(): + client = TestClient(app) response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyCookie": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyCookie": { + "type": "apiKey", + "name": "key", + "in": "cookie", + "description": "An API Cookie Key", + } + } + }, + } diff --git a/tests/test_security_api_key_cookie_optional.py b/tests/test_security_api_key_cookie_optional.py index 96a64f09a6..b8c440c9d7 100644 --- a/tests/test_security_api_key_cookie_optional.py +++ b/tests/test_security_api_key_cookie_optional.py @@ -29,47 +29,45 @@ def read_current_user(current_user: User = Depends(get_current_user)): return current_user -client = TestClient(app) - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyCookie": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_security_api_key(): - response = client.get("/users/me", cookies={"key": "secret"}) + client = TestClient(app, cookies={"key": "secret"}) + response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"username": "secret"} def test_security_api_key_no_key(): + client = TestClient(app) response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyCookie": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} + } + }, + } diff --git a/tests/test_security_api_key_header.py b/tests/test_security_api_key_header.py index d53395f999..96ad80b54a 100644 --- a/tests/test_security_api_key_header.py +++ b/tests/test_security_api_key_header.py @@ -24,37 +24,6 @@ def read_current_user(current_user: User = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyHeader": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_api_key(): response = client.get("/users/me", headers={"key": "secret"}) @@ -66,3 +35,32 @@ def test_security_api_key_no_key(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyHeader": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} + } + }, + } diff --git a/tests/test_security_api_key_header_description.py b/tests/test_security_api_key_header_description.py index cc98027080..382f53dd78 100644 --- a/tests/test_security_api_key_header_description.py +++ b/tests/test_security_api_key_header_description.py @@ -24,42 +24,6 @@ def read_current_user(current_user: User = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyHeader": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyHeader": { - "type": "apiKey", - "name": "key", - "in": "header", - "description": "An API Key Header", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_api_key(): response = client.get("/users/me", headers={"key": "secret"}) @@ -71,3 +35,37 @@ def test_security_api_key_no_key(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyHeader": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyHeader": { + "type": "apiKey", + "name": "key", + "in": "header", + "description": "An API Key Header", + } + } + }, + } diff --git a/tests/test_security_api_key_header_optional.py b/tests/test_security_api_key_header_optional.py index 4ab599c2dd..adfb20ba08 100644 --- a/tests/test_security_api_key_header_optional.py +++ b/tests/test_security_api_key_header_optional.py @@ -30,37 +30,6 @@ def read_current_user(current_user: Optional[User] = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyHeader": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_api_key(): response = client.get("/users/me", headers={"key": "secret"}) @@ -72,3 +41,32 @@ def test_security_api_key_no_key(): response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyHeader": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} + } + }, + } diff --git a/tests/test_security_api_key_query.py b/tests/test_security_api_key_query.py index 4844c65e22..da98eafd6c 100644 --- a/tests/test_security_api_key_query.py +++ b/tests/test_security_api_key_query.py @@ -24,37 +24,6 @@ def read_current_user(current_user: User = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyQuery": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_api_key(): response = client.get("/users/me?key=secret") @@ -66,3 +35,32 @@ def test_security_api_key_no_key(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyQuery": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} + } + }, + } diff --git a/tests/test_security_api_key_query_description.py b/tests/test_security_api_key_query_description.py index 9b608233a5..3c08afc5f4 100644 --- a/tests/test_security_api_key_query_description.py +++ b/tests/test_security_api_key_query_description.py @@ -24,42 +24,6 @@ def read_current_user(current_user: User = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyQuery": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyQuery": { - "type": "apiKey", - "name": "key", - "in": "query", - "description": "API Key Query", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_api_key(): response = client.get("/users/me?key=secret") @@ -71,3 +35,37 @@ def test_security_api_key_no_key(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyQuery": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyQuery": { + "type": "apiKey", + "name": "key", + "in": "query", + "description": "API Key Query", + } + } + }, + } diff --git a/tests/test_security_api_key_query_optional.py b/tests/test_security_api_key_query_optional.py index 9339b7b3aa..99a26cfd0b 100644 --- a/tests/test_security_api_key_query_optional.py +++ b/tests/test_security_api_key_query_optional.py @@ -30,37 +30,6 @@ def read_current_user(current_user: Optional[User] = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyQuery": []}], - } - } - }, - "components": { - "securitySchemes": { - "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_api_key(): response = client.get("/users/me?key=secret") @@ -72,3 +41,32 @@ def test_security_api_key_no_key(): response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyQuery": []}], + } + } + }, + "components": { + "securitySchemes": { + "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} + } + }, + } diff --git a/tests/test_security_http_base.py b/tests/test_security_http_base.py index 8947162796..d3a7542034 100644 --- a/tests/test_security_http_base.py +++ b/tests/test_security_http_base.py @@ -14,35 +14,6 @@ def read_current_user(credentials: HTTPAuthorizationCredentials = Security(secur client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBase": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_base(): response = client.get("/users/me", headers={"Authorization": "Other foobar"}) @@ -54,3 +25,30 @@ def test_security_http_base_no_credentials(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBase": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} + }, + } diff --git a/tests/test_security_http_base_description.py b/tests/test_security_http_base_description.py index 5855e8df40..3d7d150166 100644 --- a/tests/test_security_http_base_description.py +++ b/tests/test_security_http_base_description.py @@ -14,41 +14,6 @@ def read_current_user(credentials: HTTPAuthorizationCredentials = Security(secur client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBase": []}], - } - } - }, - "components": { - "securitySchemes": { - "HTTPBase": { - "type": "http", - "scheme": "Other", - "description": "Other Security Scheme", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_base(): response = client.get("/users/me", headers={"Authorization": "Other foobar"}) @@ -60,3 +25,36 @@ def test_security_http_base_no_credentials(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBase": []}], + } + } + }, + "components": { + "securitySchemes": { + "HTTPBase": { + "type": "http", + "scheme": "Other", + "description": "Other Security Scheme", + } + } + }, + } diff --git a/tests/test_security_http_base_optional.py b/tests/test_security_http_base_optional.py index 5a50f9b88c..180c9110e9 100644 --- a/tests/test_security_http_base_optional.py +++ b/tests/test_security_http_base_optional.py @@ -20,35 +20,6 @@ def read_current_user( client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBase": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_base(): response = client.get("/users/me", headers={"Authorization": "Other foobar"}) @@ -60,3 +31,30 @@ def test_security_http_base_no_credentials(): response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBase": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} + }, + } diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py index 289bd5c74c..7e7fcac32a 100644 --- a/tests/test_security_http_basic_optional.py +++ b/tests/test_security_http_basic_optional.py @@ -4,7 +4,6 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.testclient import TestClient -from requests.auth import HTTPBasicAuth app = FastAPI() @@ -20,39 +19,9 @@ def read_current_user(credentials: Optional[HTTPBasicCredentials] = Security(sec client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_basic(): - auth = HTTPBasicAuth(username="john", password="secret") - response = client.get("/users/me", auth=auth) + response = client.get("/users/me", auth=("john", "secret")) assert response.status_code == 200, response.text assert response.json() == {"username": "john", "password": "secret"} @@ -79,3 +48,30 @@ def test_security_http_basic_non_basic_credentials(): assert response.status_code == 401, response.text assert response.headers["WWW-Authenticate"] == "Basic" assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } diff --git a/tests/test_security_http_basic_realm.py b/tests/test_security_http_basic_realm.py index 54867c2e01..470afd662a 100644 --- a/tests/test_security_http_basic_realm.py +++ b/tests/test_security_http_basic_realm.py @@ -3,7 +3,6 @@ from base64 import b64encode from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.testclient import TestClient -from requests.auth import HTTPBasicAuth app = FastAPI() @@ -17,39 +16,9 @@ def read_current_user(credentials: HTTPBasicCredentials = Security(security)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_basic(): - auth = HTTPBasicAuth(username="john", password="secret") - response = client.get("/users/me", auth=auth) + response = client.get("/users/me", auth=("john", "secret")) assert response.status_code == 200, response.text assert response.json() == {"username": "john", "password": "secret"} @@ -77,3 +46,30 @@ def test_security_http_basic_non_basic_credentials(): assert response.status_code == 401, response.text assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"' assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } diff --git a/tests/test_security_http_basic_realm_description.py b/tests/test_security_http_basic_realm_description.py index 6ff9d9d07f..44289007b5 100644 --- a/tests/test_security_http_basic_realm_description.py +++ b/tests/test_security_http_basic_realm_description.py @@ -3,7 +3,6 @@ from base64 import b64encode from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.testclient import TestClient -from requests.auth import HTTPBasicAuth app = FastAPI() @@ -17,45 +16,9 @@ def read_current_user(credentials: HTTPBasicCredentials = Security(security)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], - } - } - }, - "components": { - "securitySchemes": { - "HTTPBasic": { - "type": "http", - "scheme": "basic", - "description": "HTTPBasic scheme", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_basic(): - auth = HTTPBasicAuth(username="john", password="secret") - response = client.get("/users/me", auth=auth) + response = client.get("/users/me", auth=("john", "secret")) assert response.status_code == 200, response.text assert response.json() == {"username": "john", "password": "secret"} @@ -83,3 +46,36 @@ def test_security_http_basic_non_basic_credentials(): assert response.status_code == 401, response.text assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"' assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } + } + }, + "components": { + "securitySchemes": { + "HTTPBasic": { + "type": "http", + "scheme": "basic", + "description": "HTTPBasic scheme", + } + } + }, + } diff --git a/tests/test_security_http_bearer.py b/tests/test_security_http_bearer.py index 39d8c84029..f24869fc32 100644 --- a/tests/test_security_http_bearer.py +++ b/tests/test_security_http_bearer.py @@ -14,35 +14,6 @@ def read_current_user(credentials: HTTPAuthorizationCredentials = Security(secur client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBearer": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_bearer(): response = client.get("/users/me", headers={"Authorization": "Bearer foobar"}) @@ -60,3 +31,30 @@ def test_security_http_bearer_incorrect_scheme_credentials(): response = client.get("/users/me", headers={"Authorization": "Basic notreally"}) assert response.status_code == 403, response.text assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBearer": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} + }, + } diff --git a/tests/test_security_http_bearer_description.py b/tests/test_security_http_bearer_description.py index 132e720fc1..6d5ad0b8e7 100644 --- a/tests/test_security_http_bearer_description.py +++ b/tests/test_security_http_bearer_description.py @@ -14,41 +14,6 @@ def read_current_user(credentials: HTTPAuthorizationCredentials = Security(secur client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBearer": []}], - } - } - }, - "components": { - "securitySchemes": { - "HTTPBearer": { - "type": "http", - "scheme": "bearer", - "description": "HTTP Bearer token scheme", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_bearer(): response = client.get("/users/me", headers={"Authorization": "Bearer foobar"}) @@ -66,3 +31,36 @@ def test_security_http_bearer_incorrect_scheme_credentials(): response = client.get("/users/me", headers={"Authorization": "Basic notreally"}) assert response.status_code == 403, response.text assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "HTTPBearer": { + "type": "http", + "scheme": "bearer", + "description": "HTTP Bearer token scheme", + } + } + }, + } diff --git a/tests/test_security_http_bearer_optional.py b/tests/test_security_http_bearer_optional.py index 2e7dfb8a4e..b596ac7300 100644 --- a/tests/test_security_http_bearer_optional.py +++ b/tests/test_security_http_bearer_optional.py @@ -20,35 +20,6 @@ def read_current_user( client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBearer": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_bearer(): response = client.get("/users/me", headers={"Authorization": "Bearer foobar"}) @@ -66,3 +37,30 @@ def test_security_http_bearer_incorrect_scheme_credentials(): response = client.get("/users/me", headers={"Authorization": "Basic notreally"}) assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBearer": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} + }, + } diff --git a/tests/test_security_http_digest.py b/tests/test_security_http_digest.py index 8388824ff2..2a25efe023 100644 --- a/tests/test_security_http_digest.py +++ b/tests/test_security_http_digest.py @@ -14,35 +14,6 @@ def read_current_user(credentials: HTTPAuthorizationCredentials = Security(secur client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPDigest": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_digest(): response = client.get("/users/me", headers={"Authorization": "Digest foobar"}) @@ -62,3 +33,30 @@ def test_security_http_digest_incorrect_scheme_credentials(): ) assert response.status_code == 403, response.text assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPDigest": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} + }, + } diff --git a/tests/test_security_http_digest_description.py b/tests/test_security_http_digest_description.py index d00aa1b6e3..721f7cfde5 100644 --- a/tests/test_security_http_digest_description.py +++ b/tests/test_security_http_digest_description.py @@ -14,41 +14,6 @@ def read_current_user(credentials: HTTPAuthorizationCredentials = Security(secur client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPDigest": []}], - } - } - }, - "components": { - "securitySchemes": { - "HTTPDigest": { - "type": "http", - "scheme": "digest", - "description": "HTTPDigest scheme", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_digest(): response = client.get("/users/me", headers={"Authorization": "Digest foobar"}) @@ -68,3 +33,36 @@ def test_security_http_digest_incorrect_scheme_credentials(): ) assert response.status_code == 403, response.text assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPDigest": []}], + } + } + }, + "components": { + "securitySchemes": { + "HTTPDigest": { + "type": "http", + "scheme": "digest", + "description": "HTTPDigest scheme", + } + } + }, + } diff --git a/tests/test_security_http_digest_optional.py b/tests/test_security_http_digest_optional.py index 2177b819f3..d4c3597bcb 100644 --- a/tests/test_security_http_digest_optional.py +++ b/tests/test_security_http_digest_optional.py @@ -20,35 +20,6 @@ def read_current_user( client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPDigest": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_digest(): response = client.get("/users/me", headers={"Authorization": "Digest foobar"}) @@ -68,3 +39,30 @@ def test_security_http_digest_incorrect_scheme_credentials(): ) assert response.status_code == 403, response.text assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPDigest": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} + }, + } diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py index b9ac488eea..23dce7cfa9 100644 --- a/tests/test_security_oauth2.py +++ b/tests/test_security_oauth2.py @@ -40,124 +40,6 @@ def read_current_user(current_user: "User" = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/login": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_login_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_login_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OAuth2": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_login_post": { - "title": "Body_login_login_post", - "required": ["grant_type", "username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "read:users": "Read the users", - "write:users": "Create users", - }, - "tokenUrl": "token", - } - }, - } - }, - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_oauth2(): response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) @@ -247,3 +129,121 @@ def test_strict_login(data, expected_status, expected_response): response = client.post("/login", data=data) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_login_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_login_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OAuth2": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_login_post": { + "title": "Body_login_login_post", + "required": ["grant_type", "username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "read:users": "Read the users", + "write:users": "Create users", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_security_oauth2_authorization_code_bearer.py b/tests/test_security_oauth2_authorization_code_bearer.py index ad9a39ded7..6df81528db 100644 --- a/tests/test_security_oauth2_authorization_code_bearer.py +++ b/tests/test_security_oauth2_authorization_code_bearer.py @@ -18,46 +18,6 @@ async def read_items(token: Optional[str] = Security(oauth2_scheme)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2AuthorizationCodeBearer": []}], - } - } - }, - "components": { - "securitySchemes": { - "OAuth2AuthorizationCodeBearer": { - "type": "oauth2", - "flows": { - "authorizationCode": { - "authorizationUrl": "authorize", - "tokenUrl": "token", - "scopes": {}, - } - }, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_no_token(): response = client.get("/items") @@ -75,3 +35,41 @@ def test_token(): response = client.get("/items", headers={"Authorization": "Bearer testtoken"}) assert response.status_code == 200, response.text assert response.json() == {"token": "testtoken"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2AuthorizationCodeBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2AuthorizationCodeBearer": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "authorize", + "tokenUrl": "token", + "scopes": {}, + } + }, + } + } + }, + } diff --git a/tests/test_security_oauth2_authorization_code_bearer_description.py b/tests/test_security_oauth2_authorization_code_bearer_description.py index bdaa543fc3..c119abde4d 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_description.py +++ b/tests/test_security_oauth2_authorization_code_bearer_description.py @@ -21,47 +21,6 @@ async def read_items(token: Optional[str] = Security(oauth2_scheme)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2AuthorizationCodeBearer": []}], - } - } - }, - "components": { - "securitySchemes": { - "OAuth2AuthorizationCodeBearer": { - "type": "oauth2", - "flows": { - "authorizationCode": { - "authorizationUrl": "authorize", - "tokenUrl": "token", - "scopes": {}, - } - }, - "description": "OAuth2 Code Bearer", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_no_token(): response = client.get("/items") @@ -79,3 +38,42 @@ def test_token(): response = client.get("/items", headers={"Authorization": "Bearer testtoken"}) assert response.status_code == 200, response.text assert response.json() == {"token": "testtoken"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2AuthorizationCodeBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2AuthorizationCodeBearer": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "authorize", + "tokenUrl": "token", + "scopes": {}, + } + }, + "description": "OAuth2 Code Bearer", + } + } + }, + } diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index a5fd49b8c7..3ef9f4a8d2 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -44,124 +44,6 @@ def read_users_me(current_user: Optional[User] = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/login": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_login_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_login_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_login_post": { - "title": "Body_login_login_post", - "required": ["grant_type", "username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "read:users": "Read the users", - "write:users": "Create users", - }, - "tokenUrl": "token", - } - }, - } - }, - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_oauth2(): response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) @@ -251,3 +133,121 @@ def test_strict_login(data, expected_status, expected_response): response = client.post("/login", data=data) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_login_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_login_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_login_post": { + "title": "Body_login_login_post", + "required": ["grant_type", "username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "read:users": "Read the users", + "write:users": "Create users", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index 171f96b762..b6425fde4d 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -45,125 +45,6 @@ def read_users_me(current_user: Optional[User] = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/login": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_login_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_login_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_login_post": { - "title": "Body_login_login_post", - "required": ["grant_type", "username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "read:users": "Read the users", - "write:users": "Create users", - }, - "tokenUrl": "token", - } - }, - "description": "OAuth2 security scheme", - } - }, - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_oauth2(): response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) @@ -253,3 +134,122 @@ def test_strict_login(data, expected_status, expected_response): response = client.post("/login", data=data) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_login_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_login_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_login_post": { + "title": "Body_login_login_post", + "required": ["grant_type", "username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "read:users": "Read the users", + "write:users": "Create users", + }, + "tokenUrl": "token", + } + }, + "description": "OAuth2 security scheme", + } + }, + }, + } diff --git a/tests/test_security_oauth2_password_bearer_optional.py b/tests/test_security_oauth2_password_bearer_optional.py index 3d6637d4a5..e5dcbb553d 100644 --- a/tests/test_security_oauth2_password_bearer_optional.py +++ b/tests/test_security_oauth2_password_bearer_optional.py @@ -18,40 +18,6 @@ async def read_items(token: Optional[str] = Security(oauth2_scheme)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2PasswordBearer": []}], - } - } - }, - "components": { - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_no_token(): response = client.get("/items") @@ -69,3 +35,35 @@ def test_incorrect_token(): response = client.get("/items", headers={"Authorization": "Notexistent testtoken"}) assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, + } + } + }, + } diff --git a/tests/test_security_oauth2_password_bearer_optional_description.py b/tests/test_security_oauth2_password_bearer_optional_description.py index 9d6a862e3c..9ff48e7156 100644 --- a/tests/test_security_oauth2_password_bearer_optional_description.py +++ b/tests/test_security_oauth2_password_bearer_optional_description.py @@ -22,41 +22,6 @@ async def read_items(token: Optional[str] = Security(oauth2_scheme)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2PasswordBearer": []}], - } - } - }, - "components": { - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, - "description": "OAuth2PasswordBearer security scheme", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_no_token(): response = client.get("/items") @@ -74,3 +39,36 @@ def test_incorrect_token(): response = client.get("/items", headers={"Authorization": "Notexistent testtoken"}) assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, + "description": "OAuth2PasswordBearer security scheme", + } + } + }, + } diff --git a/tests/test_security_openid_connect.py b/tests/test_security_openid_connect.py index 8203961bed..206de6574f 100644 --- a/tests/test_security_openid_connect.py +++ b/tests/test_security_openid_connect.py @@ -24,37 +24,6 @@ def read_current_user(current_user: User = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OpenIdConnect": []}], - } - } - }, - "components": { - "securitySchemes": { - "OpenIdConnect": {"type": "openIdConnect", "openIdConnectUrl": "/openid"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_oauth2(): response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) @@ -72,3 +41,35 @@ def test_security_oauth2_password_bearer_no_header(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OpenIdConnect": []}], + } + } + }, + "components": { + "securitySchemes": { + "OpenIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "/openid", + } + } + }, + } diff --git a/tests/test_security_openid_connect_description.py b/tests/test_security_openid_connect_description.py index 218cbfc8f7..5884de7930 100644 --- a/tests/test_security_openid_connect_description.py +++ b/tests/test_security_openid_connect_description.py @@ -26,41 +26,6 @@ def read_current_user(current_user: User = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OpenIdConnect": []}], - } - } - }, - "components": { - "securitySchemes": { - "OpenIdConnect": { - "type": "openIdConnect", - "openIdConnectUrl": "/openid", - "description": "OpenIdConnect security scheme", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_oauth2(): response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) @@ -78,3 +43,36 @@ def test_security_oauth2_password_bearer_no_header(): response = client.get("/users/me") assert response.status_code == 403, response.text assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OpenIdConnect": []}], + } + } + }, + "components": { + "securitySchemes": { + "OpenIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "/openid", + "description": "OpenIdConnect security scheme", + } + } + }, + } diff --git a/tests/test_security_openid_connect_optional.py b/tests/test_security_openid_connect_optional.py index 4577dfebb0..8ac7191183 100644 --- a/tests/test_security_openid_connect_optional.py +++ b/tests/test_security_openid_connect_optional.py @@ -30,37 +30,6 @@ def read_current_user(current_user: Optional[User] = Depends(get_current_user)): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OpenIdConnect": []}], - } - } - }, - "components": { - "securitySchemes": { - "OpenIdConnect": {"type": "openIdConnect", "openIdConnectUrl": "/openid"} - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_oauth2(): response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"}) @@ -78,3 +47,35 @@ def test_security_oauth2_password_bearer_no_header(): response = client.get("/users/me") assert response.status_code == 200, response.text assert response.json() == {"msg": "Create an account first"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OpenIdConnect": []}], + } + } + }, + "components": { + "securitySchemes": { + "OpenIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "/openid", + } + } + }, + } diff --git a/tests/test_serialize_response_dataclass.py b/tests/test_serialize_response_dataclass.py index e520338ec6..1e3bf3b28b 100644 --- a/tests/test_serialize_response_dataclass.py +++ b/tests/test_serialize_response_dataclass.py @@ -1,8 +1,9 @@ +from dataclasses import dataclass +from datetime import datetime from typing import List, Optional from fastapi import FastAPI from fastapi.testclient import TestClient -from pydantic.dataclasses import dataclass app = FastAPI() @@ -10,54 +11,64 @@ app = FastAPI() @dataclass class Item: name: str + date: datetime price: Optional[float] = None owner_ids: Optional[List[int]] = None @app.get("/items/valid", response_model=Item) def get_valid(): - return {"name": "valid", "price": 1.0} + return {"name": "valid", "date": datetime(2021, 7, 26), "price": 1.0} @app.get("/items/object", response_model=Item) def get_object(): - return Item(name="object", price=1.0, owner_ids=[1, 2, 3]) + return Item( + name="object", date=datetime(2021, 7, 26), price=1.0, owner_ids=[1, 2, 3] + ) @app.get("/items/coerce", response_model=Item) def get_coerce(): - return {"name": "coerce", "price": "1.0"} + return {"name": "coerce", "date": datetime(2021, 7, 26).isoformat(), "price": "1.0"} @app.get("/items/validlist", response_model=List[Item]) def get_validlist(): return [ - {"name": "foo"}, - {"name": "bar", "price": 1.0}, - {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, + {"name": "foo", "date": datetime(2021, 7, 26)}, + {"name": "bar", "date": datetime(2021, 7, 26), "price": 1.0}, + { + "name": "baz", + "date": datetime(2021, 7, 26), + "price": 2.0, + "owner_ids": [1, 2, 3], + }, ] @app.get("/items/objectlist", response_model=List[Item]) def get_objectlist(): return [ - Item(name="foo"), - Item(name="bar", price=1.0), - Item(name="baz", price=2.0, owner_ids=[1, 2, 3]), + Item(name="foo", date=datetime(2021, 7, 26)), + Item(name="bar", date=datetime(2021, 7, 26), price=1.0), + Item(name="baz", date=datetime(2021, 7, 26), price=2.0, owner_ids=[1, 2, 3]), ] @app.get("/items/no-response-model/object") def get_no_response_model_object(): - return Item(name="object", price=1.0, owner_ids=[1, 2, 3]) + return Item( + name="object", date=datetime(2021, 7, 26), price=1.0, owner_ids=[1, 2, 3] + ) @app.get("/items/no-response-model/objectlist") def get_no_response_model_objectlist(): return [ - Item(name="foo"), - Item(name="bar", price=1.0), - Item(name="baz", price=2.0, owner_ids=[1, 2, 3]), + Item(name="foo", date=datetime(2021, 7, 26)), + Item(name="bar", date=datetime(2021, 7, 26), price=1.0), + Item(name="baz", date=datetime(2021, 7, 26), price=2.0, owner_ids=[1, 2, 3]), ] @@ -67,28 +78,58 @@ client = TestClient(app) def test_valid(): response = client.get("/items/valid") response.raise_for_status() - assert response.json() == {"name": "valid", "price": 1.0, "owner_ids": None} + assert response.json() == { + "name": "valid", + "date": datetime(2021, 7, 26).isoformat(), + "price": 1.0, + "owner_ids": None, + } def test_object(): response = client.get("/items/object") response.raise_for_status() - assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]} + assert response.json() == { + "name": "object", + "date": datetime(2021, 7, 26).isoformat(), + "price": 1.0, + "owner_ids": [1, 2, 3], + } def test_coerce(): response = client.get("/items/coerce") response.raise_for_status() - assert response.json() == {"name": "coerce", "price": 1.0, "owner_ids": None} + assert response.json() == { + "name": "coerce", + "date": datetime(2021, 7, 26).isoformat(), + "price": 1.0, + "owner_ids": None, + } def test_validlist(): response = client.get("/items/validlist") response.raise_for_status() assert response.json() == [ - {"name": "foo", "price": None, "owner_ids": None}, - {"name": "bar", "price": 1.0, "owner_ids": None}, - {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, + { + "name": "foo", + "date": datetime(2021, 7, 26).isoformat(), + "price": None, + "owner_ids": None, + }, + { + "name": "bar", + "date": datetime(2021, 7, 26).isoformat(), + "price": 1.0, + "owner_ids": None, + }, + { + "name": "baz", + "date": datetime(2021, 7, 26).isoformat(), + "price": 2.0, + "owner_ids": [1, 2, 3], + }, ] @@ -96,23 +137,58 @@ def test_objectlist(): response = client.get("/items/objectlist") response.raise_for_status() assert response.json() == [ - {"name": "foo", "price": None, "owner_ids": None}, - {"name": "bar", "price": 1.0, "owner_ids": None}, - {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, + { + "name": "foo", + "date": datetime(2021, 7, 26).isoformat(), + "price": None, + "owner_ids": None, + }, + { + "name": "bar", + "date": datetime(2021, 7, 26).isoformat(), + "price": 1.0, + "owner_ids": None, + }, + { + "name": "baz", + "date": datetime(2021, 7, 26).isoformat(), + "price": 2.0, + "owner_ids": [1, 2, 3], + }, ] def test_no_response_model_object(): response = client.get("/items/no-response-model/object") response.raise_for_status() - assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]} + assert response.json() == { + "name": "object", + "date": datetime(2021, 7, 26).isoformat(), + "price": 1.0, + "owner_ids": [1, 2, 3], + } def test_no_response_model_objectlist(): response = client.get("/items/no-response-model/objectlist") response.raise_for_status() assert response.json() == [ - {"name": "foo", "price": None, "owner_ids": None}, - {"name": "bar", "price": 1.0, "owner_ids": None}, - {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, + { + "name": "foo", + "date": datetime(2021, 7, 26).isoformat(), + "price": None, + "owner_ids": None, + }, + { + "name": "bar", + "date": datetime(2021, 7, 26).isoformat(), + "price": 1.0, + "owner_ids": None, + }, + { + "name": "baz", + "date": datetime(2021, 7, 26).isoformat(), + "price": 2.0, + "owner_ids": [1, 2, 3], + }, ] diff --git a/tests/test_serialize_response_model.py b/tests/test_serialize_response_model.py index 2956674376..3bb46b2e9b 100644 --- a/tests/test_serialize_response_model.py +++ b/tests/test_serialize_response_model.py @@ -8,7 +8,7 @@ app = FastAPI() class Item(BaseModel): - name: str = Field(..., alias="aliased_name") + name: str = Field(alias="aliased_name") price: Optional[float] = None owner_ids: Optional[List[int]] = None diff --git a/tests/test_starlette_exception.py b/tests/test_starlette_exception.py index 859169d3cd..96f835b935 100644 --- a/tests/test_starlette_exception.py +++ b/tests/test_starlette_exception.py @@ -18,6 +18,16 @@ async def read_item(item_id: str): return {"item": items[item_id]} +@app.get("/http-no-body-statuscode-exception") +async def no_body_status_code_exception(): + raise HTTPException(status_code=204) + + +@app.get("/http-no-body-statuscode-with-detail-exception") +async def no_body_status_code_with_detail_exception(): + raise HTTPException(status_code=204, detail="I should just disappear!") + + @app.get("/starlette-items/{item_id}") async def read_starlette_item(item_id: str): if item_id not in items: @@ -27,108 +37,6 @@ async def read_starlette_item(item_id: str): client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/starlette-items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Starlette Item", - "operationId": "read_starlette_item_starlette_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_item(): response = client.get("/items/foo") @@ -154,3 +62,141 @@ def test_get_starlette_item_not_found(): assert response.status_code == 404, response.text assert response.headers.get("x-error") is None assert response.json() == {"detail": "Item not found"} + + +def test_no_body_status_code_exception_handlers(): + response = client.get("/http-no-body-statuscode-exception") + assert response.status_code == 204 + assert not response.content + + +def test_no_body_status_code_with_detail_exception_handlers(): + response = client.get("/http-no-body-statuscode-with-detail-exception") + assert response.status_code == 204 + assert not response.content + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/http-no-body-statuscode-exception": { + "get": { + "operationId": "no_body_status_code_exception_http_no_body_statuscode_exception_get", + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + } + }, + "summary": "No Body Status Code Exception", + } + }, + "/http-no-body-statuscode-with-detail-exception": { + "get": { + "operationId": "no_body_status_code_with_detail_exception_http_no_body_statuscode_with_detail_exception_get", + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + } + }, + "summary": "No Body Status Code With Detail Exception", + } + }, + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/starlette-items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Starlette Item", + "operationId": "read_starlette_item_starlette_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_starlette_urlconvertors.py b/tests/test_starlette_urlconvertors.py index 2320c7005f..5ef1b819cd 100644 --- a/tests/test_starlette_urlconvertors.py +++ b/tests/test_starlette_urlconvertors.py @@ -1,24 +1,29 @@ -from fastapi import FastAPI, Path +from fastapi import FastAPI, Path, Query from fastapi.testclient import TestClient app = FastAPI() @app.get("/int/{param:int}") -def int_convertor(param: int = Path(...)): +def int_convertor(param: int = Path()): return {"int": param} @app.get("/float/{param:float}") -def float_convertor(param: float = Path(...)): +def float_convertor(param: float = Path()): return {"float": param} @app.get("/path/{param:path}") -def path_convertor(param: str = Path(...)): +def path_convertor(param: str = Path()): return {"path": param} +@app.get("/query/") +def query_convertor(param: str = Query()): + return {"query": param} + + client = TestClient(app) @@ -45,6 +50,13 @@ def test_route_converters_path(): assert response.json() == {"path": "some/example"} +def test_route_converters_query(): + # Test query conversion + response = client.get("/query", params={"param": "Qué tal!"}) + assert response.status_code == 200, response.text + assert response.json() == {"query": "Qué tal!"} + + def test_url_path_for_path_convertor(): assert ( app.url_path_for("path_convertor", param="some/example") == "/path/some/example" diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py index 7574d6fbc5..c5a0237e80 100644 --- a/tests/test_sub_callbacks.py +++ b/tests/test_sub_callbacks.py @@ -74,205 +74,6 @@ app.include_router(subrouter, callbacks=events_callback_router.routes) client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/invoices/": { - "post": { - "summary": "Create Invoice", - "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', - "operationId": "create_invoice_invoices__post", - "parameters": [ - { - "required": False, - "schema": { - "title": "Callback Url", - "maxLength": 2083, - "minLength": 1, - "type": "string", - "format": "uri", - }, - "name": "callback_url", - "in": "query", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Invoice"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "callbacks": { - "event_callback": { - "{$callback_url}/events/{$request.body.title}": { - "get": { - "summary": "Event Callback", - "operationId": "event_callback__callback_url__events___request_body_title__get", - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Event" - } - } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "invoice_notification": { - "{$callback_url}/invoices/{$request.body.id}": { - "post": { - "summary": "Invoice Notification", - "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEvent" - } - } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEventReceived" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - } - }, - "components": { - "schemas": { - "Event": { - "title": "Event", - "required": ["name", "total"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "total": {"title": "Total", "type": "number"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Invoice": { - "title": "Invoice", - "required": ["id", "customer", "total"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - "customer": {"title": "Customer", "type": "string"}, - "total": {"title": "Total", "type": "number"}, - }, - }, - "InvoiceEvent": { - "title": "InvoiceEvent", - "required": ["description", "paid"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "paid": {"title": "Paid", "type": "boolean"}, - }, - }, - "InvoiceEventReceived": { - "title": "InvoiceEventReceived", - "required": ["ok"], - "type": "object", - "properties": {"ok": {"title": "Ok", "type": "boolean"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi(): - with client: - response = client.get("/openapi.json") - - assert response.json() == openapi_schema - def test_get(): response = client.post( @@ -280,3 +81,205 @@ def test_get(): ) assert response.status_code == 200, response.text assert response.json() == {"msg": "Invoice received"} + + +def test_openapi_schema(): + with client: + response = client.get("/openapi.json") + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/invoices/": { + "post": { + "summary": "Create Invoice", + "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', + "operationId": "create_invoice_invoices__post", + "parameters": [ + { + "required": False, + "schema": { + "title": "Callback Url", + "maxLength": 2083, + "minLength": 1, + "type": "string", + "format": "uri", + }, + "name": "callback_url", + "in": "query", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Invoice"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "callbacks": { + "event_callback": { + "{$callback_url}/events/{$request.body.title}": { + "get": { + "summary": "Event Callback", + "operationId": "event_callback__callback_url__events___request_body_title__get", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "invoice_notification": { + "{$callback_url}/invoices/{$request.body.id}": { + "post": { + "summary": "Invoice Notification", + "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEvent" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEventReceived" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + } + }, + "components": { + "schemas": { + "Event": { + "title": "Event", + "required": ["name", "total"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "total": {"title": "Total", "type": "number"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Invoice": { + "title": "Invoice", + "required": ["id", "customer", "total"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + "customer": {"title": "Customer", "type": "string"}, + "total": {"title": "Total", "type": "number"}, + }, + }, + "InvoiceEvent": { + "title": "InvoiceEvent", + "required": ["description", "paid"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "paid": {"title": "Paid", "type": "boolean"}, + }, + }, + "InvoiceEventReceived": { + "title": "InvoiceEventReceived", + "required": ["ok"], + "type": "object", + "properties": {"ok": {"title": "Ok", "type": "boolean"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tuples.py b/tests/test_tuples.py index 2085dc3670..4fa1f7a133 100644 --- a/tests/test_tuples.py +++ b/tests/test_tuples.py @@ -27,195 +27,12 @@ def post_tuple_of_models(square: Tuple[Coordinate, Coordinate]): @app.post("/tuple-form/") -def hello(values: Tuple[int, int] = Form(...)): +def hello(values: Tuple[int, int] = Form()): return values client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/model-with-tuple/": { - "post": { - "summary": "Post Model With Tuple", - "operationId": "post_model_with_tuple_model_with_tuple__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemGroup"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/tuple-of-models/": { - "post": { - "summary": "Post Tuple Of Models", - "operationId": "post_tuple_of_models_tuple_of_models__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Square", - "maxItems": 2, - "minItems": 2, - "type": "array", - "items": [ - {"$ref": "#/components/schemas/Coordinate"}, - {"$ref": "#/components/schemas/Coordinate"}, - ], - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/tuple-form/": { - "post": { - "summary": "Hello", - "operationId": "hello_tuple_form__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_hello_tuple_form__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_hello_tuple_form__post": { - "title": "Body_hello_tuple_form__post", - "required": ["values"], - "type": "object", - "properties": { - "values": { - "title": "Values", - "maxItems": 2, - "minItems": 2, - "type": "array", - "items": [{"type": "integer"}, {"type": "integer"}], - } - }, - }, - "Coordinate": { - "title": "Coordinate", - "required": ["x", "y"], - "type": "object", - "properties": { - "x": {"title": "X", "type": "number"}, - "y": {"title": "Y", "type": "number"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ItemGroup": { - "title": "ItemGroup", - "required": ["items"], - "type": "object", - "properties": { - "items": { - "title": "Items", - "type": "array", - "items": { - "maxItems": 2, - "minItems": 2, - "type": "array", - "items": [{"type": "string"}, {"type": "string"}], - }, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_model_with_tuple_valid(): data = {"items": [["foo", "bar"], ["baz", "whatelse"]]} @@ -252,16 +69,197 @@ def test_tuple_with_model_invalid(): def test_tuple_form_valid(): - response = client.post("/tuple-form/", data=[("values", "1"), ("values", "2")]) + response = client.post("/tuple-form/", data={"values": ("1", "2")}) assert response.status_code == 200, response.text assert response.json() == [1, 2] def test_tuple_form_invalid(): - response = client.post( - "/tuple-form/", data=[("values", "1"), ("values", "2"), ("values", "3")] - ) + response = client.post("/tuple-form/", data={"values": ("1", "2", "3")}) assert response.status_code == 422, response.text - response = client.post("/tuple-form/", data=[("values", "1")]) + response = client.post("/tuple-form/", data={"values": ("1")}) assert response.status_code == 422, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/model-with-tuple/": { + "post": { + "summary": "Post Model With Tuple", + "operationId": "post_model_with_tuple_model_with_tuple__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemGroup"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/tuple-of-models/": { + "post": { + "summary": "Post Tuple Of Models", + "operationId": "post_tuple_of_models_tuple_of_models__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Square", + "maxItems": 2, + "minItems": 2, + "type": "array", + "items": [ + {"$ref": "#/components/schemas/Coordinate"}, + {"$ref": "#/components/schemas/Coordinate"}, + ], + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/tuple-form/": { + "post": { + "summary": "Hello", + "operationId": "hello_tuple_form__post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_hello_tuple_form__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_hello_tuple_form__post": { + "title": "Body_hello_tuple_form__post", + "required": ["values"], + "type": "object", + "properties": { + "values": { + "title": "Values", + "maxItems": 2, + "minItems": 2, + "type": "array", + "items": [{"type": "integer"}, {"type": "integer"}], + } + }, + }, + "Coordinate": { + "title": "Coordinate", + "required": ["x", "y"], + "type": "object", + "properties": { + "x": {"title": "X", "type": "number"}, + "y": {"title": "Y", "type": "number"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ItemGroup": { + "title": "ItemGroup", + "required": ["items"], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "maxItems": 2, + "minItems": 2, + "type": "array", + "items": [{"type": "string"}, {"type": "string"}], + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py index 1a8acb5231..3d6267023e 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py @@ -4,105 +4,6 @@ from docs_src.additional_responses.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Message"} - } - }, - }, - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "Message": { - "title": "Message", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_path_operation(): response = client.get("/items/foo") @@ -114,3 +15,102 @@ def test_path_operation_not_found(): response = client.get("/items/bar") assert response.status_code == 404, response.text assert response.json() == {"message": "Item not found"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Message"} + } + }, + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, + }, + }, + "Message": { + "title": "Message", + "required": ["message"], + "type": "object", + "properties": {"message": {"title": "Message", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index 2adcf15d07..6182ed5079 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -7,98 +7,6 @@ from docs_src.additional_responses.tutorial002 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Return the JSON item or an image.", - "content": { - "image/png": {}, - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - }, - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": {"title": "Img", "type": "boolean"}, - "name": "img", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_path_operation(): response = client.get("/items/foo") @@ -113,3 +21,95 @@ def test_path_operation_img(): assert response.headers["Content-Type"] == "image/png" assert len(response.content) os.remove("./image.png") + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Return the JSON item or an image.", + "content": { + "image/png": {}, + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + }, + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": {"title": "Img", "type": "boolean"}, + "name": "img", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py index 8b2167de05..77568d9d4f 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py @@ -4,106 +4,6 @@ from docs_src.additional_responses.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "404": { - "description": "The item was not found", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Message"} - } - }, - }, - "200": { - "description": "Item requested by ID", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "example": {"id": "bar", "value": "The bar tenders"}, - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "Message": { - "title": "Message", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_path_operation(): response = client.get("/items/foo") @@ -115,3 +15,106 @@ def test_path_operation_not_found(): response = client.get("/items/bar") assert response.status_code == 404, response.text assert response.json() == {"message": "Item not found"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "404": { + "description": "The item was not found", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Message"} + } + }, + }, + "200": { + "description": "Item requested by ID", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "example": { + "id": "bar", + "value": "The bar tenders", + }, + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, + }, + }, + "Message": { + "title": "Message", + "required": ["message"], + "type": "object", + "properties": {"message": {"title": "Message", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index 990d5235aa..3fbd91e5c2 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -7,101 +7,6 @@ from docs_src.additional_responses.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "404": {"description": "Item not found"}, - "302": {"description": "The item was moved"}, - "403": {"description": "Not enough privileges"}, - "200": { - "description": "Successful Response", - "content": { - "image/png": {}, - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - }, - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": {"title": "Img", "type": "boolean"}, - "name": "img", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_path_operation(): response = client.get("/items/foo") @@ -116,3 +21,98 @@ def test_path_operation_img(): assert response.headers["Content-Type"] == "image/png" assert len(response.content) os.remove("./image.png") + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "404": {"description": "Item not found"}, + "302": {"description": "The item was moved"}, + "403": {"description": "Not enough privileges"}, + "200": { + "description": "Successful Response", + "content": { + "image/png": {}, + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + }, + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": {"title": "Img", "type": "boolean"}, + "name": "img", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an.py new file mode 100644 index 0000000000..2cb2bb9930 --- /dev/null +++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an.py @@ -0,0 +1,17 @@ +from fastapi.testclient import TestClient + +from docs_src.additional_status_codes.tutorial001_an import app + +client = TestClient(app) + + +def test_update(): + response = client.put("/items/foo", json={"name": "Wrestlers"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Wrestlers", "size": None} + + +def test_create(): + response = client.put("/items/red", json={"name": "Chillies"}) + assert response.status_code == 201, response.text + assert response.json() == {"name": "Chillies", "size": None} diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py310.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..c7660a3925 --- /dev/null +++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py310.py @@ -0,0 +1,26 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.additional_status_codes.tutorial001_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_update(client: TestClient): + response = client.put("/items/foo", json={"name": "Wrestlers"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Wrestlers", "size": None} + + +@needs_py310 +def test_create(client: TestClient): + response = client.put("/items/red", json={"name": "Chillies"}) + assert response.status_code == 201, response.text + assert response.json() == {"name": "Chillies", "size": None} diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py39.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..303c5dbae2 --- /dev/null +++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py39.py @@ -0,0 +1,26 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.additional_status_codes.tutorial001_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_update(client: TestClient): + response = client.put("/items/foo", json={"name": "Wrestlers"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Wrestlers", "size": None} + + +@needs_py39 +def test_create(client: TestClient): + response = client.put("/items/red", json={"name": "Chillies"}) + assert response.status_code == 201, response.text + assert response.json() == {"name": "Chillies", "size": None} diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_py310.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_py310.py new file mode 100644 index 0000000000..02f2e188c7 --- /dev/null +++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_py310.py @@ -0,0 +1,26 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.additional_status_codes.tutorial001_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_update(client: TestClient): + response = client.put("/items/foo", json={"name": "Wrestlers"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Wrestlers", "size": None} + + +@needs_py310 +def test_create(client: TestClient): + response = client.put("/items/red", json={"name": "Chillies"}) + assert response.status_code == 201, response.text + assert response.json() == {"name": "Chillies", "size": None} diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py index 17165c0fc6..157fa5caf1 100644 --- a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py @@ -9,6 +9,6 @@ def test_middleware(): assert response.status_code == 200, response.text client = TestClient(app) - response = client.get("/", allow_redirects=False) + response = client.get("/", follow_redirects=False) assert response.status_code == 307, response.text assert response.headers["location"] == "https://testserver/" diff --git a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py index 1ad625db6a..3da362a505 100644 --- a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py +++ b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py @@ -2,120 +2,6 @@ from fastapi.testclient import TestClient from docs_src.async_sql_databases.tutorial001 import app -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/notes/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Notes Notes Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Note"}, - } - } - }, - } - }, - "summary": "Read Notes", - "operationId": "read_notes_notes__get", - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Note"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Note", - "operationId": "create_note_notes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/NoteIn"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "NoteIn": { - "title": "NoteIn", - "required": ["text", "completed"], - "type": "object", - "properties": { - "text": {"title": "Text", "type": "string"}, - "completed": {"title": "Completed", "type": "boolean"}, - }, - }, - "Note": { - "title": "Note", - "required": ["id", "text", "completed"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "integer"}, - "text": {"title": "Text", "type": "string"}, - "completed": {"title": "Completed", "type": "boolean"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - with TestClient(app) as client: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_create_read(): with TestClient(app) as client: @@ -129,3 +15,121 @@ def test_create_read(): response = client.get("/notes/") assert response.status_code == 200, response.text assert data in response.json() + + +def test_openapi_schema(): + with TestClient(app) as client: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/notes/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Notes Notes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Note" + }, + } + } + }, + } + }, + "summary": "Read Notes", + "operationId": "read_notes_notes__get", + }, + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Note"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Note", + "operationId": "create_note_notes__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/NoteIn"} + } + }, + "required": True, + }, + }, + } + }, + "components": { + "schemas": { + "NoteIn": { + "title": "NoteIn", + "required": ["text", "completed"], + "type": "object", + "properties": { + "text": {"title": "Text", "type": "string"}, + "completed": {"title": "Completed", "type": "boolean"}, + }, + }, + "Note": { + "title": "Note", + "required": ["id", "text", "completed"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "integer"}, + "text": {"title": "Text", "type": "string"}, + "completed": {"title": "Completed", "type": "boolean"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002_an.py b/tests/test_tutorial/test_background_tasks/test_tutorial002_an.py new file mode 100644 index 0000000000..af682ecff1 --- /dev/null +++ b/tests/test_tutorial/test_background_tasks/test_tutorial002_an.py @@ -0,0 +1,19 @@ +import os +from pathlib import Path + +from fastapi.testclient import TestClient + +from docs_src.background_tasks.tutorial002_an import app + +client = TestClient(app) + + +def test(): + log = Path("log.txt") + if log.is_file(): + os.remove(log) # pragma: no cover + response = client.post("/send-notification/foo@example.com?q=some-query") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Message sent"} + with open("./log.txt") as f: + assert "found query: some-query\nmessage to foo@example.com" in f.read() diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py310.py b/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py310.py new file mode 100644 index 0000000000..067b2787e9 --- /dev/null +++ b/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py310.py @@ -0,0 +1,21 @@ +import os +from pathlib import Path + +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@needs_py310 +def test(): + from docs_src.background_tasks.tutorial002_an_py310 import app + + client = TestClient(app) + log = Path("log.txt") + if log.is_file(): + os.remove(log) # pragma: no cover + response = client.post("/send-notification/foo@example.com?q=some-query") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Message sent"} + with open("./log.txt") as f: + assert "found query: some-query\nmessage to foo@example.com" in f.read() diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py39.py b/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py39.py new file mode 100644 index 0000000000..06b5a2f57a --- /dev/null +++ b/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py39.py @@ -0,0 +1,21 @@ +import os +from pathlib import Path + +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@needs_py39 +def test(): + from docs_src.background_tasks.tutorial002_an_py39 import app + + client = TestClient(app) + log = Path("log.txt") + if log.is_file(): + os.remove(log) # pragma: no cover + response = client.post("/send-notification/foo@example.com?q=some-query") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Message sent"} + with open("./log.txt") as f: + assert "found query: some-query\nmessage to foo@example.com" in f.read() diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py index be9e499bf8..7533a1b689 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py @@ -4,34 +4,32 @@ from docs_src.behind_a_proxy.tutorial001 import app client = TestClient(app, root_path="/api/v1") -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, - "servers": [{"url": "/api/v1"}], -} - - -def test_openapi(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - def test_main(): response = client.get("/app") assert response.status_code == 200 assert response.json() == {"message": "Hello World", "root_path": "/api/v1"} + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + "servers": [{"url": "/api/v1"}], + } diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py index ac192e3db7..930ab3bf5c 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py @@ -4,34 +4,32 @@ from docs_src.behind_a_proxy.tutorial002 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, - "servers": [{"url": "/api/v1"}], -} - - -def test_openapi(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - def test_main(): response = client.get("/app") assert response.status_code == 200 assert response.json() == {"message": "Hello World", "root_path": "/api/v1"} + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + "servers": [{"url": "/api/v1"}], + } diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py index 2727525ca4..ae8f1a495d 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py @@ -4,38 +4,39 @@ from docs_src.behind_a_proxy.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "servers": [ - {"url": "/api/v1"}, - {"url": "https://stag.example.com", "description": "Staging environment"}, - {"url": "https://prod.example.com", "description": "Production environment"}, - ], - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} - - -def test_openapi(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - def test_main(): response = client.get("/app") assert response.status_code == 200 assert response.json() == {"message": "Hello World", "root_path": "/api/v1"} + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "servers": [ + {"url": "/api/v1"}, + {"url": "https://stag.example.com", "description": "Staging environment"}, + { + "url": "https://prod.example.com", + "description": "Production environment", + }, + ], + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py index 4c4e4b75c2..e67ad1cb1f 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py @@ -4,37 +4,38 @@ from docs_src.behind_a_proxy.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "servers": [ - {"url": "https://stag.example.com", "description": "Staging environment"}, - {"url": "https://prod.example.com", "description": "Production environment"}, - ], - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} - - -def test_openapi(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - def test_main(): response = client.get("/app") assert response.status_code == 200 assert response.json() == {"message": "Hello World", "root_path": "/api/v1"} + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "servers": [ + {"url": "https://stag.example.com", "description": "Staging environment"}, + { + "url": "https://prod.example.com", + "description": "Production environment", + }, + ], + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py index cd6d7b5c8b..a13decd75b 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main.py +++ b/tests/test_tutorial/test_bigger_applications/test_main.py @@ -5,334 +5,6 @@ from docs_src.bigger_applications.app.main import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "tags": ["users"], - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/me": { - "get": { - "tags": ["users"], - "summary": "Read User Me", - "operationId": "read_user_me_users_me_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/{username}": { - "get": { - "tags": ["users"], - "summary": "Read User", - "operationId": "read_user_users__username__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Username", "type": "string"}, - "name": "username", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/items/": { - "get": { - "tags": ["items"], - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "404": {"description": "Not found"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/items/{item_id}": { - "get": { - "tags": ["items"], - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "404": {"description": "Not found"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "put": { - "tags": ["items", "custom"], - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "404": {"description": "Not found"}, - "403": {"description": "Operation forbidden"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/admin/": { - "post": { - "tags": ["admin"], - "summary": "Update Admin", - "operationId": "update_admin_admin__post", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "418": {"description": "I'm a teapot"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/": { - "get": { - "summary": "Root", - "operationId": "root__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - no_jessica = { "detail": [ @@ -427,7 +99,6 @@ no_jessica = { ), ("/?token=jessica", 200, {"message": "Hello Bigger Applications!"}, {}), ("/", 422, no_jessica, {}), - ("/openapi.json", 200, openapi_schema, {}), ], ) def test_get_path(path, expected_status, expected_response, headers): @@ -489,3 +160,337 @@ def test_admin_invalid_header(): response = client.post("/admin/", headers={"X-Token": "invalid"}) assert response.status_code == 400, response.text assert response.json() == {"detail": "X-Token header invalid"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/me": { + "get": { + "tags": ["users"], + "summary": "Read User Me", + "operationId": "read_user_me_users_me_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/{username}": { + "get": { + "tags": ["users"], + "summary": "Read User", + "operationId": "read_user_users__username__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Username", "type": "string"}, + "name": "username", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/": { + "get": { + "tags": ["items"], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/{item_id}": { + "get": { + "tags": ["items"], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "put": { + "tags": ["items", "custom"], + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "403": {"description": "Operation forbidden"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/admin/": { + "post": { + "tags": ["admin"], + "summary": "Update Admin", + "operationId": "update_admin_admin__post", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "418": {"description": "I'm a teapot"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_bigger_applications/test_main_an.py b/tests/test_tutorial/test_bigger_applications/test_main_an.py new file mode 100644 index 0000000000..64e19c3f39 --- /dev/null +++ b/tests/test_tutorial/test_bigger_applications/test_main_an.py @@ -0,0 +1,496 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.bigger_applications.app_an.main import app + +client = TestClient(app) + + +no_jessica = { + "detail": [ + { + "loc": ["query", "token"], + "msg": "field required", + "type": "value_error.missing", + }, + ] +} + + +@pytest.mark.parametrize( + "path,expected_status,expected_response,headers", + [ + ( + "/users?token=jessica", + 200, + [{"username": "Rick"}, {"username": "Morty"}], + {}, + ), + ("/users", 422, no_jessica, {}), + ("/users/foo?token=jessica", 200, {"username": "foo"}, {}), + ("/users/foo", 422, no_jessica, {}), + ("/users/me?token=jessica", 200, {"username": "fakecurrentuser"}, {}), + ("/users/me", 422, no_jessica, {}), + ( + "/users?token=monica", + 400, + {"detail": "No Jessica token provided"}, + {}, + ), + ( + "/items?token=jessica", + 200, + {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}, + {"X-Token": "fake-super-secret-token"}, + ), + ("/items", 422, no_jessica, {"X-Token": "fake-super-secret-token"}), + ( + "/items/plumbus?token=jessica", + 200, + {"name": "Plumbus", "item_id": "plumbus"}, + {"X-Token": "fake-super-secret-token"}, + ), + ( + "/items/bar?token=jessica", + 404, + {"detail": "Item not found"}, + {"X-Token": "fake-super-secret-token"}, + ), + ("/items/plumbus", 422, no_jessica, {"X-Token": "fake-super-secret-token"}), + ( + "/items?token=jessica", + 400, + {"detail": "X-Token header invalid"}, + {"X-Token": "invalid"}, + ), + ( + "/items/bar?token=jessica", + 400, + {"detail": "X-Token header invalid"}, + {"X-Token": "invalid"}, + ), + ( + "/items?token=jessica", + 422, + { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + } + ] + }, + {}, + ), + ( + "/items/plumbus?token=jessica", + 422, + { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + } + ] + }, + {}, + ), + ("/?token=jessica", 200, {"message": "Hello Bigger Applications!"}, {}), + ("/", 422, no_jessica, {}), + ], +) +def test_get_path(path, expected_status, expected_response, headers): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_put_no_header(): + response = client.put("/items/foo") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["query", "token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +def test_put_invalid_header(): + response = client.put("/items/foo", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +def test_put(): + response = client.put( + "/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"item_id": "plumbus", "name": "The great Plumbus"} + + +def test_put_forbidden(): + response = client.put( + "/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"} + ) + assert response.status_code == 403, response.text + assert response.json() == {"detail": "You can only update the item: plumbus"} + + +def test_admin(): + response = client.post( + "/admin/?token=jessica", headers={"X-Token": "fake-super-secret-token"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Admin getting schwifty"} + + +def test_admin_invalid_header(): + response = client.post("/admin/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/me": { + "get": { + "tags": ["users"], + "summary": "Read User Me", + "operationId": "read_user_me_users_me_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/{username}": { + "get": { + "tags": ["users"], + "summary": "Read User", + "operationId": "read_user_users__username__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Username", "type": "string"}, + "name": "username", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/": { + "get": { + "tags": ["items"], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/{item_id}": { + "get": { + "tags": ["items"], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "put": { + "tags": ["items", "custom"], + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "403": {"description": "Operation forbidden"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/admin/": { + "post": { + "tags": ["admin"], + "summary": "Update Admin", + "operationId": "update_admin_admin__post", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "418": {"description": "I'm a teapot"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py b/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py new file mode 100644 index 0000000000..70c86b4d7f --- /dev/null +++ b/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py @@ -0,0 +1,511 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + +no_jessica = { + "detail": [ + { + "loc": ["query", "token"], + "msg": "field required", + "type": "value_error.missing", + }, + ] +} + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.bigger_applications.app_an_py39.main import app + + client = TestClient(app) + return client + + +@needs_py39 +@pytest.mark.parametrize( + "path,expected_status,expected_response,headers", + [ + ( + "/users?token=jessica", + 200, + [{"username": "Rick"}, {"username": "Morty"}], + {}, + ), + ("/users", 422, no_jessica, {}), + ("/users/foo?token=jessica", 200, {"username": "foo"}, {}), + ("/users/foo", 422, no_jessica, {}), + ("/users/me?token=jessica", 200, {"username": "fakecurrentuser"}, {}), + ("/users/me", 422, no_jessica, {}), + ( + "/users?token=monica", + 400, + {"detail": "No Jessica token provided"}, + {}, + ), + ( + "/items?token=jessica", + 200, + {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}, + {"X-Token": "fake-super-secret-token"}, + ), + ("/items", 422, no_jessica, {"X-Token": "fake-super-secret-token"}), + ( + "/items/plumbus?token=jessica", + 200, + {"name": "Plumbus", "item_id": "plumbus"}, + {"X-Token": "fake-super-secret-token"}, + ), + ( + "/items/bar?token=jessica", + 404, + {"detail": "Item not found"}, + {"X-Token": "fake-super-secret-token"}, + ), + ("/items/plumbus", 422, no_jessica, {"X-Token": "fake-super-secret-token"}), + ( + "/items?token=jessica", + 400, + {"detail": "X-Token header invalid"}, + {"X-Token": "invalid"}, + ), + ( + "/items/bar?token=jessica", + 400, + {"detail": "X-Token header invalid"}, + {"X-Token": "invalid"}, + ), + ( + "/items?token=jessica", + 422, + { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + } + ] + }, + {}, + ), + ( + "/items/plumbus?token=jessica", + 422, + { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + } + ] + }, + {}, + ), + ("/?token=jessica", 200, {"message": "Hello Bigger Applications!"}, {}), + ("/", 422, no_jessica, {}), + ], +) +def test_get_path( + path, expected_status, expected_response, headers, client: TestClient +): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_put_no_header(client: TestClient): + response = client.put("/items/foo") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["query", "token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +@needs_py39 +def test_put_invalid_header(client: TestClient): + response = client.put("/items/foo", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +@needs_py39 +def test_put(client: TestClient): + response = client.put( + "/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"item_id": "plumbus", "name": "The great Plumbus"} + + +@needs_py39 +def test_put_forbidden(client: TestClient): + response = client.put( + "/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"} + ) + assert response.status_code == 403, response.text + assert response.json() == {"detail": "You can only update the item: plumbus"} + + +@needs_py39 +def test_admin(client: TestClient): + response = client.post( + "/admin/?token=jessica", headers={"X-Token": "fake-super-secret-token"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Admin getting schwifty"} + + +@needs_py39 +def test_admin_invalid_header(client: TestClient): + response = client.post("/admin/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/me": { + "get": { + "tags": ["users"], + "summary": "Read User Me", + "operationId": "read_user_me_users_me_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/{username}": { + "get": { + "tags": ["users"], + "summary": "Read User", + "operationId": "read_user_users__username__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Username", "type": "string"}, + "name": "username", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/": { + "get": { + "tags": ["items"], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/{item_id}": { + "get": { + "tags": ["items"], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "put": { + "tags": ["items", "custom"], + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "403": {"description": "Operation forbidden"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/admin/": { + "post": { + "tags": ["admin"], + "summary": "Update Admin", + "operationId": "update_admin_admin__post", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "418": {"description": "I'm a teapot"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index 8dbaf15dbe..cd1209adea 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -7,89 +7,6 @@ from docs_src.body.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - price_missing = { "detail": [ @@ -176,7 +93,7 @@ def test_post_broken_body(): response = client.post( "/items/", headers={"content-type": "application/json"}, - data="{some broken json}", + content="{some broken json}", ) assert response.status_code == 422, response.text assert response.json() == { @@ -214,7 +131,7 @@ def test_post_form_for_json(): def test_explicit_content_type(): response = client.post( "/items/", - data='{"name": "Foo", "price": 50.5}', + content='{"name": "Foo", "price": 50.5}', headers={"Content-Type": "application/json"}, ) assert response.status_code == 200, response.text @@ -223,7 +140,7 @@ def test_explicit_content_type(): def test_geo_json(): response = client.post( "/items/", - data='{"name": "Foo", "price": 50.5}', + content='{"name": "Foo", "price": 50.5}', headers={"Content-Type": "application/geo+json"}, ) assert response.status_code == 200, response.text @@ -232,7 +149,7 @@ def test_geo_json(): def test_no_content_type_is_json(): response = client.post( "/items/", - data='{"name": "Foo", "price": 50.5}', + content='{"name": "Foo", "price": 50.5}', ) assert response.status_code == 200, response.text assert response.json() == { @@ -255,17 +172,19 @@ def test_wrong_headers(): ] } - response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"}) + response = client.post( + "/items/", content=data, headers={"Content-Type": "text/plain"} + ) assert response.status_code == 422, response.text assert response.json() == invalid_dict response = client.post( - "/items/", data=data, headers={"Content-Type": "application/geo+json-seq"} + "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"} ) assert response.status_code == 422, response.text assert response.json() == invalid_dict response = client.post( - "/items/", data=data, headers={"Content-Type": "application/not-really-json"} + "/items/", content=data, headers={"Content-Type": "application/not-really-json"} ) assert response.status_code == 422, response.text assert response.json() == invalid_dict @@ -275,3 +194,86 @@ def test_other_exceptions(): with patch("json.loads", side_effect=Exception): response = client.post("/items/", json={"test": "test2"}) assert response.status_code == 400, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py index dd9d9911e4..5ebcbbf574 100644 --- a/tests/test_tutorial/test_body/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body/test_tutorial001_py310.py @@ -5,83 +5,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture def client(): @@ -91,13 +14,6 @@ def client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - price_missing = { "detail": [ { @@ -185,7 +101,7 @@ def test_post_broken_body(client: TestClient): response = client.post( "/items/", headers={"content-type": "application/json"}, - data="{some broken json}", + content="{some broken json}", ) assert response.status_code == 422, response.text assert response.json() == { @@ -225,7 +141,7 @@ def test_post_form_for_json(client: TestClient): def test_explicit_content_type(client: TestClient): response = client.post( "/items/", - data='{"name": "Foo", "price": 50.5}', + content='{"name": "Foo", "price": 50.5}', headers={"Content-Type": "application/json"}, ) assert response.status_code == 200, response.text @@ -235,7 +151,7 @@ def test_explicit_content_type(client: TestClient): def test_geo_json(client: TestClient): response = client.post( "/items/", - data='{"name": "Foo", "price": 50.5}', + content='{"name": "Foo", "price": 50.5}', headers={"Content-Type": "application/geo+json"}, ) assert response.status_code == 200, response.text @@ -245,7 +161,7 @@ def test_geo_json(client: TestClient): def test_no_content_type_is_json(client: TestClient): response = client.post( "/items/", - data='{"name": "Foo", "price": 50.5}', + content='{"name": "Foo", "price": 50.5}', ) assert response.status_code == 200, response.text assert response.json() == { @@ -269,17 +185,19 @@ def test_wrong_headers(client: TestClient): ] } - response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"}) + response = client.post( + "/items/", content=data, headers={"Content-Type": "text/plain"} + ) assert response.status_code == 422, response.text assert response.json() == invalid_dict response = client.post( - "/items/", data=data, headers={"Content-Type": "application/geo+json-seq"} + "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"} ) assert response.status_code == 422, response.text assert response.json() == invalid_dict response = client.post( - "/items/", data=data, headers={"Content-Type": "application/not-really-json"} + "/items/", content=data, headers={"Content-Type": "application/not-really-json"} ) assert response.status_code == 422, response.text assert response.json() == invalid_dict @@ -290,3 +208,87 @@ def test_other_exceptions(client: TestClient): with patch("json.loads", side_effect=Exception): response = client.post("/items/", json={"test": "test2"}) assert response.status_code == 400, response.text + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py index fe5a270f36..a7ea0e9496 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py @@ -6,115 +6,6 @@ from docs_src.body_fields.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "The description of the item", - "maxLength": 300, - "type": "string", - }, - "price": { - "title": "Price", - "exclusiveMinimum": 0.0, - "type": "number", - "description": "The price must be greater than zero", - }, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item"], - "type": "object", - "properties": {"item": {"$ref": "#/components/schemas/Item"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - price_not_greater = { "detail": [ { @@ -167,3 +58,111 @@ def test(path, body, expected_status, expected_response): response = client.put(path, json=body) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "The description of the item", + "maxLength": 300, + "type": "string", + }, + "price": { + "title": "Price", + "exclusiveMinimum": 0.0, + "type": "number", + "description": "The price must be greater than zero", + }, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item"], + "type": "object", + "properties": {"item": {"$ref": "#/components/schemas/Item"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_an.py b/tests/test_tutorial/test_body_fields/test_tutorial001_an.py new file mode 100644 index 0000000000..32f996ecbd --- /dev/null +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_an.py @@ -0,0 +1,168 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.body_fields.tutorial001_an import app + +client = TestClient(app) + + +price_not_greater = { + "detail": [ + { + "ctx": {"limit_value": 0}, + "loc": ["body", "item", "price"], + "msg": "ensure this value is greater than 0", + "type": "value_error.number.not_gt", + } + ] +} + + +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5", + {"item": {"name": "Foo", "price": 3.0}}, + 200, + { + "item_id": 5, + "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None}, + }, + ), + ( + "/items/6", + { + "item": { + "name": "Bar", + "price": 0.2, + "description": "Some bar", + "tax": "5.4", + } + }, + 200, + { + "item_id": 6, + "item": { + "name": "Bar", + "price": 0.2, + "description": "Some bar", + "tax": 5.4, + }, + }, + ), + ("/items/5", {"item": {"name": "Foo", "price": -3.0}}, 422, price_not_greater), + ], +) +def test(path, body, expected_status, expected_response): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "The description of the item", + "maxLength": 300, + "type": "string", + }, + "price": { + "title": "Price", + "exclusiveMinimum": 0.0, + "type": "number", + "description": "The price must be greater than zero", + }, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item"], + "type": "object", + "properties": {"item": {"$ref": "#/components/schemas/Item"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..20e032fcd8 --- /dev/null +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py @@ -0,0 +1,176 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.body_fields.tutorial001_an_py310 import app + + client = TestClient(app) + return client + + +price_not_greater = { + "detail": [ + { + "ctx": {"limit_value": 0}, + "loc": ["body", "item", "price"], + "msg": "ensure this value is greater than 0", + "type": "value_error.number.not_gt", + } + ] +} + + +@needs_py310 +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5", + {"item": {"name": "Foo", "price": 3.0}}, + 200, + { + "item_id": 5, + "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None}, + }, + ), + ( + "/items/6", + { + "item": { + "name": "Bar", + "price": 0.2, + "description": "Some bar", + "tax": "5.4", + } + }, + 200, + { + "item_id": 6, + "item": { + "name": "Bar", + "price": 0.2, + "description": "Some bar", + "tax": 5.4, + }, + }, + ), + ("/items/5", {"item": {"name": "Foo", "price": -3.0}}, 422, price_not_greater), + ], +) +def test(path, body, expected_status, expected_response, client: TestClient): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "The description of the item", + "maxLength": 300, + "type": "string", + }, + "price": { + "title": "Price", + "exclusiveMinimum": 0.0, + "type": "number", + "description": "The price must be greater than zero", + }, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item"], + "type": "object", + "properties": {"item": {"$ref": "#/components/schemas/Item"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..e3baf5f2ba --- /dev/null +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py @@ -0,0 +1,176 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.body_fields.tutorial001_an_py39 import app + + client = TestClient(app) + return client + + +price_not_greater = { + "detail": [ + { + "ctx": {"limit_value": 0}, + "loc": ["body", "item", "price"], + "msg": "ensure this value is greater than 0", + "type": "value_error.number.not_gt", + } + ] +} + + +@needs_py39 +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5", + {"item": {"name": "Foo", "price": 3.0}}, + 200, + { + "item_id": 5, + "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None}, + }, + ), + ( + "/items/6", + { + "item": { + "name": "Bar", + "price": 0.2, + "description": "Some bar", + "tax": "5.4", + } + }, + 200, + { + "item_id": 6, + "item": { + "name": "Bar", + "price": 0.2, + "description": "Some bar", + "tax": 5.4, + }, + }, + ), + ("/items/5", {"item": {"name": "Foo", "price": -3.0}}, 422, price_not_greater), + ], +) +def test(path, body, expected_status, expected_response, client: TestClient): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "The description of the item", + "maxLength": 300, + "type": "string", + }, + "price": { + "title": "Price", + "exclusiveMinimum": 0.0, + "type": "number", + "description": "The price must be greater than zero", + }, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item"], + "type": "object", + "properties": {"item": {"$ref": "#/components/schemas/Item"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py index 993e2a91d2..4c2f48674a 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py @@ -3,108 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "The description of the item", - "maxLength": 300, - "type": "string", - }, - "price": { - "title": "Price", - "exclusiveMinimum": 0.0, - "type": "number", - "description": "The price must be greater than zero", - }, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item"], - "type": "object", - "properties": {"item": {"$ref": "#/components/schemas/Item"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -114,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - price_not_greater = { "detail": [ { @@ -174,3 +65,112 @@ def test(path, body, expected_status, expected_response, client: TestClient): response = client.put(path, json=body) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "The description of the item", + "maxLength": 300, + "type": "string", + }, + "price": { + "title": "Price", + "exclusiveMinimum": 0.0, + "type": "number", + "description": "The price must be greater than zero", + }, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item"], + "type": "object", + "properties": {"item": {"$ref": "#/components/schemas/Item"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py index 8dc710d755..496ab38fb9 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py @@ -5,107 +5,6 @@ from docs_src.body_multiple_params.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "maximum": 1000.0, - "minimum": 0.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - } - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - item_id_not_int = { "detail": [ @@ -145,3 +44,104 @@ def test_post_body(path, body, expected_status, expected_response): response = client.put(path, json=body) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "maximum": 1000.0, + "minimum": 0.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + } + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py new file mode 100644 index 0000000000..74a8a9b2e4 --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py @@ -0,0 +1,147 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.body_multiple_params.tutorial001_an import app + +client = TestClient(app) + + +item_id_not_int = { + "detail": [ + { + "loc": ["path", "item_id"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ] +} + + +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5?q=bar", + {"name": "Foo", "price": 50.5}, + 200, + { + "item_id": 5, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "q": "bar", + }, + ), + ("/items/5?q=bar", None, 200, {"item_id": 5, "q": "bar"}), + ("/items/5", None, 200, {"item_id": 5}), + ("/items/foo", None, 422, item_id_not_int), + ], +) +def test_post_body(path, body, expected_status, expected_response): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "maximum": 1000.0, + "minimum": 0.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + } + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..9c764b6d17 --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py @@ -0,0 +1,155 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.body_multiple_params.tutorial001_an_py310 import app + + client = TestClient(app) + return client + + +item_id_not_int = { + "detail": [ + { + "loc": ["path", "item_id"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ] +} + + +@needs_py310 +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5?q=bar", + {"name": "Foo", "price": 50.5}, + 200, + { + "item_id": 5, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "q": "bar", + }, + ), + ("/items/5?q=bar", None, 200, {"item_id": 5, "q": "bar"}), + ("/items/5", None, 200, {"item_id": 5}), + ("/items/foo", None, 422, item_id_not_int), + ], +) +def test_post_body(path, body, expected_status, expected_response, client: TestClient): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "maximum": 1000.0, + "minimum": 0.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + } + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..0cca294333 --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py @@ -0,0 +1,155 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.body_multiple_params.tutorial001_an_py39 import app + + client = TestClient(app) + return client + + +item_id_not_int = { + "detail": [ + { + "loc": ["path", "item_id"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ] +} + + +@needs_py39 +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5?q=bar", + {"name": "Foo", "price": 50.5}, + 200, + { + "item_id": 5, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "q": "bar", + }, + ), + ("/items/5?q=bar", None, 200, {"item_id": 5, "q": "bar"}), + ("/items/5", None, 200, {"item_id": 5}), + ("/items/foo", None, 422, item_id_not_int), + ], +) +def test_post_body(path, body, expected_status, expected_response, client: TestClient): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "maximum": 1000.0, + "minimum": 0.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + } + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py index 5114ccea29..3b61e717ec 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py @@ -3,101 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "maximum": 1000.0, - "minimum": 0.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - } - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -107,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - item_id_not_int = { "detail": [ { @@ -153,3 +51,105 @@ def test_post_body(path, body, expected_status, expected_response, client: TestC response = client.put(path, json=body) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "maximum": 1000.0, + "minimum": 0.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + } + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py index 64aa9c43bf..b34377a28c 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -5,118 +5,6 @@ from docs_src.body_multiple_params.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": {"title": "Full Name", "type": "string"}, - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - # Test required and embedded body parameters with no bodies sent @pytest.mark.parametrize( @@ -196,3 +84,115 @@ def test_post_body(path, body, expected_status, expected_response): response = client.put(path, json=body) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item", "user", "importance"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "user": {"$ref": "#/components/schemas/User"}, + "importance": {"title": "Importance", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py new file mode 100644 index 0000000000..9b8d5e15ba --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py @@ -0,0 +1,198 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.body_multiple_params.tutorial003_an import app + +client = TestClient(app) + + +# Test required and embedded body parameters with no bodies sent +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5", + { + "importance": 2, + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "Dave"}, + }, + 200, + { + "item_id": 5, + "importance": 2, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "user": {"username": "Dave", "full_name": None}, + }, + ), + ( + "/items/5", + None, + 422, + { + "detail": [ + { + "loc": ["body", "item"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "user"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "importance"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + }, + ), + ( + "/items/5", + [], + 422, + { + "detail": [ + { + "loc": ["body", "item"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "user"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "importance"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + }, + ), + ], +) +def test_post_body(path, body, expected_status, expected_response): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item", "user", "importance"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "user": {"$ref": "#/components/schemas/User"}, + "importance": {"title": "Importance", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py new file mode 100644 index 0000000000..f8af555fcd --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py @@ -0,0 +1,206 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.body_multiple_params.tutorial003_an_py310 import app + + client = TestClient(app) + return client + + +# Test required and embedded body parameters with no bodies sent +@needs_py310 +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5", + { + "importance": 2, + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "Dave"}, + }, + 200, + { + "item_id": 5, + "importance": 2, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "user": {"username": "Dave", "full_name": None}, + }, + ), + ( + "/items/5", + None, + 422, + { + "detail": [ + { + "loc": ["body", "item"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "user"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "importance"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + }, + ), + ( + "/items/5", + [], + 422, + { + "detail": [ + { + "loc": ["body", "item"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "user"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "importance"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + }, + ), + ], +) +def test_post_body(path, body, expected_status, expected_response, client: TestClient): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item", "user", "importance"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "user": {"$ref": "#/components/schemas/User"}, + "importance": {"title": "Importance", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py new file mode 100644 index 0000000000..06e2c3146f --- /dev/null +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py @@ -0,0 +1,206 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.body_multiple_params.tutorial003_an_py39 import app + + client = TestClient(app) + return client + + +# Test required and embedded body parameters with no bodies sent +@needs_py39 +@pytest.mark.parametrize( + "path,body,expected_status,expected_response", + [ + ( + "/items/5", + { + "importance": 2, + "item": {"name": "Foo", "price": 50.5}, + "user": {"username": "Dave"}, + }, + 200, + { + "item_id": 5, + "importance": 2, + "item": { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + }, + "user": {"username": "Dave", "full_name": None}, + }, + ), + ( + "/items/5", + None, + 422, + { + "detail": [ + { + "loc": ["body", "item"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "user"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "importance"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + }, + ), + ( + "/items/5", + [], + 422, + { + "detail": [ + { + "loc": ["body", "item"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "user"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "importance"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + }, + ), + ], +) +def test_post_body(path, body, expected_status, expected_response, client: TestClient): + response = client.put(path, json=body) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item", "user", "importance"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "user": {"$ref": "#/components/schemas/User"}, + "importance": {"title": "Importance", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py index fc019d8bb4..82c5fb1013 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py @@ -3,112 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": {"title": "Full Name", "type": "string"}, - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -118,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - # Test required and embedded body parameters with no bodies sent @needs_py310 @pytest.mark.parametrize( @@ -204,3 +91,116 @@ def test_post_body(path, body, expected_status, expected_response, client: TestC response = client.put(path, json=body) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item", "user", "importance"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "user": {"$ref": "#/components/schemas/User"}, + "importance": {"title": "Importance", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py index c56d41b5bf..378c241975 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py @@ -4,82 +4,6 @@ from docs_src.body_nested_models.tutorial009 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/index-weights/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Index Weights", - "operationId": "create_index_weights_index_weights__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Weights", - "type": "object", - "additionalProperties": {"type": "number"}, - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post_body(): data = {"2": 2.2, "3": 3.3} @@ -101,3 +25,79 @@ def test_post_invalid_body(): } ] } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/index-weights/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Index Weights", + "operationId": "create_index_weights_index_weights__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Weights", + "type": "object", + "additionalProperties": {"type": "number"}, + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py index 5b8d828612..5ca63a92bd 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py @@ -3,76 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/index-weights/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Index Weights", - "operationId": "create_index_weights_index_weights__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Weights", - "type": "object", - "additionalProperties": {"type": "number"}, - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -82,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_post_body(client: TestClient): data = {"2": 2.2, "3": 3.3} @@ -111,3 +34,80 @@ def test_post_invalid_body(client: TestClient): } ] } + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/index-weights/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Index Weights", + "operationId": "create_index_weights_index_weights__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Weights", + "type": "object", + "additionalProperties": {"type": "number"}, + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index efd0e46765..939bf44e0a 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -4,138 +4,6 @@ from docs_src.body_updates.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get(): response = client.get("/items/baz") @@ -160,3 +28,135 @@ def test_put(): "tax": 10.5, "tags": [], } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + }, + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py index 49279b3206..5f50f2071a 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py @@ -3,132 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -138,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_get(client: TestClient): response = client.get("/items/baz") @@ -170,3 +37,136 @@ def test_put(client: TestClient): "tax": 10.5, "tags": [], } + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + }, + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py index 872530bcf9..d4fdabce66 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py @@ -3,132 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -138,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_get(client: TestClient): response = client.get("/items/baz") @@ -170,3 +37,136 @@ def test_put(client: TestClient): "tax": 10.5, "tags": [], } + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + }, + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py index 93c8775ce5..c1d8fd8051 100644 --- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py @@ -4,35 +4,6 @@ from fastapi.testclient import TestClient from docs_src.conditional_openapi import tutorial001 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "summary": "Root", - "operationId": "root__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} - - -def test_default_openapi(): - client = TestClient(tutorial001.app) - response = client.get("/openapi.json") - assert response.json() == openapi_schema - response = client.get("/docs") - assert response.status_code == 200, response.text - response = client.get("/redoc") - assert response.status_code == 200, response.text - def test_disable_openapi(monkeypatch): monkeypatch.setenv("OPENAPI_URL", "") @@ -51,3 +22,31 @@ def test_root(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"message": "Hello World"} + + +def test_default_openapi(): + importlib.reload(tutorial001) + client = TestClient(tutorial001.app) + response = client.get("/docs") + assert response.status_code == 200, response.text + response = client.get("/redoc") + assert response.status_code == 200, response.text + response = client.get("/openapi.json") + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py index edccffec1e..c3511d129e 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py @@ -3,79 +3,10 @@ from fastapi.testclient import TestClient from docs_src.cookie_params.tutorial001 import app -client = TestClient(app) - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Ads Id", "type": "string"}, - "name": "ads_id", - "in": "cookie", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.mark.parametrize( "path,cookies,expected_status,expected_response", [ - ("/openapi.json", None, 200, openapi_schema), ("/items", None, 200, {"ads_id": None}), ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), ( @@ -88,6 +19,80 @@ openapi_schema = { ], ) def test(path, cookies, expected_status, expected_response): - response = client.get(path, cookies=cookies) + client = TestClient(app, cookies=cookies) + response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Ads Id", "type": "string"}, + "name": "ads_id", + "in": "cookie", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py new file mode 100644 index 0000000000..f4f94c09dc --- /dev/null +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py @@ -0,0 +1,98 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.cookie_params.tutorial001_an import app + + +@pytest.mark.parametrize( + "path,cookies,expected_status,expected_response", + [ + ("/items", None, 200, {"ads_id": None}), + ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), + ( + "/items", + {"ads_id": "ads_track", "session": "cookiesession"}, + 200, + {"ads_id": "ads_track"}, + ), + ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), + ], +) +def test(path, cookies, expected_status, expected_response): + client = TestClient(app, cookies=cookies) + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Ads Id", "type": "string"}, + "name": "ads_id", + "in": "cookie", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..a80f10f810 --- /dev/null +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py @@ -0,0 +1,104 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@needs_py310 +@pytest.mark.parametrize( + "path,cookies,expected_status,expected_response", + [ + ("/items", None, 200, {"ads_id": None}), + ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), + ( + "/items", + {"ads_id": "ads_track", "session": "cookiesession"}, + 200, + {"ads_id": "ads_track"}, + ), + ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), + ], +) +def test(path, cookies, expected_status, expected_response): + from docs_src.cookie_params.tutorial001_an_py310 import app + + client = TestClient(app, cookies=cookies) + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(): + from docs_src.cookie_params.tutorial001_an_py310 import app + + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Ads Id", "type": "string"}, + "name": "ads_id", + "in": "cookie", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..1be898c092 --- /dev/null +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py @@ -0,0 +1,104 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@needs_py39 +@pytest.mark.parametrize( + "path,cookies,expected_status,expected_response", + [ + ("/items", None, 200, {"ads_id": None}), + ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), + ( + "/items", + {"ads_id": "ads_track", "session": "cookiesession"}, + 200, + {"ads_id": "ads_track"}, + ), + ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), + ], +) +def test(path, cookies, expected_status, expected_response): + from docs_src.cookie_params.tutorial001_an_py39 import app + + client = TestClient(app, cookies=cookies) + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(): + from docs_src.cookie_params.tutorial001_an_py39 import app + + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Ads Id", "type": "string"}, + "name": "ads_id", + "in": "cookie", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py index 5caa5c4400..7ba542c902 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py @@ -3,86 +3,11 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Ads Id", "type": "string"}, - "name": "ads_id", - "in": "cookie", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.cookie_params.tutorial001_py310 import app - - client = TestClient(app) - return client - @needs_py310 @pytest.mark.parametrize( "path,cookies,expected_status,expected_response", [ - ("/openapi.json", None, 200, openapi_schema), ("/items", None, 200, {"ads_id": None}), ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), ( @@ -94,7 +19,86 @@ def get_client(): ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), ], ) -def test(path, cookies, expected_status, expected_response, client: TestClient): - response = client.get(path, cookies=cookies) +def test(path, cookies, expected_status, expected_response): + from docs_src.cookie_params.tutorial001_py310 import app + + client = TestClient(app, cookies=cookies) + response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(): + from docs_src.cookie_params.tutorial001_py310 import app + + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Ads Id", "type": "string"}, + "name": "ads_id", + "in": "cookie", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py index 3eb5822e28..e6da630e88 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py @@ -26,7 +26,7 @@ def test_gzip_request(compress): data = gzip.compress(data) headers["Content-Encoding"] = "gzip" headers["Content-Type"] = "application/json" - response = client.post("/sum", data=data, headers=headers) + response = client.post("/sum", content=data, headers=headers) assert response.json() == {"sum": n} diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index 430076f88f..da2ca8d620 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -4,33 +4,31 @@ from docs_src.custom_response.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_custom_response(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py index 0f15d5f48c..f681f5a9d0 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py @@ -4,33 +4,31 @@ from docs_src.custom_response.tutorial001b import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_custom_response(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial004.py index 5d75cce960..ef0ba34469 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial004.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial004.py @@ -4,24 +4,6 @@ from docs_src.custom_response.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"text/html": {"schema": {"type": "string"}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, -} html_contents = """ @@ -35,13 +17,30 @@ html_contents = """ """ -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_get_custom_response(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.text == html_contents + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"text/html": {"schema": {"type": "string"}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial005.py b/tests/test_tutorial/test_custom_response/test_tutorial005.py index ecf6ee2b98..e4b5c15466 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial005.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial005.py @@ -4,33 +4,31 @@ from docs_src.custom_response.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "summary": "Main", - "operationId": "main__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"text/plain": {"schema": {"type": "string"}}}, - } - }, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get(): response = client.get("/") assert response.status_code == 200, response.text assert response.text == "Hello World" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Main", + "operationId": "main__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"text/plain": {"schema": {"type": "string"}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py index 72bbfd2777..9f1b07bee9 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py @@ -5,33 +5,30 @@ from docs_src.custom_response.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/typer": { - "get": { - "summary": "Redirect Typer", - "operationId": "redirect_typer_typer_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} +def test_get(): + response = client.get("/typer", follow_redirects=False) + assert response.status_code == 307, response.text + assert response.headers["location"] == "https://typer.tiangolo.com" def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_get(): - response = client.get("/typer", allow_redirects=False) - assert response.status_code == 307, response.text - assert response.headers["location"] == "https://typer.tiangolo.com" + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/typer": { + "get": { + "summary": "Redirect Typer", + "operationId": "redirect_typer_typer_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py index ac5a76d34d..cf204cbc05 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py @@ -5,28 +5,25 @@ from docs_src.custom_response.tutorial006b import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/fastapi": { - "get": { - "summary": "Redirect Fastapi", - "operationId": "redirect_fastapi_fastapi_get", - "responses": {"307": {"description": "Successful Response"}}, - } - } - }, -} +def test_redirect_response_class(): + response = client.get("/fastapi", follow_redirects=False) + assert response.status_code == 307 + assert response.headers["location"] == "https://fastapi.tiangolo.com" def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_redirect_response_class(): - response = client.get("/fastapi", allow_redirects=False) - assert response.status_code == 307 - assert response.headers["location"] == "https://fastapi.tiangolo.com" + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/fastapi": { + "get": { + "summary": "Redirect Fastapi", + "operationId": "redirect_fastapi_fastapi_get", + "responses": {"307": {"description": "Successful Response"}}, + } + } + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py index 009225e8c5..f196899aca 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006c.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py @@ -5,28 +5,25 @@ from docs_src.custom_response.tutorial006c import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/pydantic": { - "get": { - "summary": "Redirect Pydantic", - "operationId": "redirect_pydantic_pydantic_get", - "responses": {"302": {"description": "Successful Response"}}, - } - } - }, -} +def test_redirect_status_code(): + response = client.get("/pydantic", follow_redirects=False) + assert response.status_code == 302 + assert response.headers["location"] == "https://pydantic-docs.helpmanual.io/" def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_redirect_status_code(): - response = client.get("/pydantic", allow_redirects=False) - assert response.status_code == 302 - assert response.headers["location"] == "https://pydantic-docs.helpmanual.io/" + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/pydantic": { + "get": { + "summary": "Redirect Pydantic", + "operationId": "redirect_pydantic_pydantic_get", + "responses": {"302": {"description": "Successful Response"}}, + } + } + }, + } diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009c.py b/tests/test_tutorial/test_custom_response/test_tutorial009c.py new file mode 100644 index 0000000000..23c711abe9 --- /dev/null +++ b/tests/test_tutorial/test_custom_response/test_tutorial009c.py @@ -0,0 +1,10 @@ +from fastapi.testclient import TestClient + +from docs_src.custom_response.tutorial009c import app + +client = TestClient(app) + + +def test_get(): + response = client.get("/") + assert response.content == b'{\n "message": "Hello World"\n}' diff --git a/docs/tr/overrides/.gitignore b/tests/test_tutorial/test_dataclasses/__init__.py similarity index 100% rename from docs/tr/overrides/.gitignore rename to tests/test_tutorial/test_dataclasses/__init__.py diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index bf15641949..26ae04c515 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -4,89 +4,6 @@ from docs_src.dataclasses.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - def test_post_item(): response = client.post("/items/", json={"name": "Foo", "price": 3}) @@ -111,3 +28,86 @@ def test_post_invalid_item(): } ] } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index 34aeb0be5f..675e826b11 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -1,71 +1,9 @@ -from copy import deepcopy - from fastapi.testclient import TestClient from docs_src.dataclasses.tutorial002 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/next": { - "get": { - "summary": "Read Next Item", - "operationId": "read_next_item_items_next_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - } - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - }, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - }, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200 - # TODO: remove this once Pydantic 1.9 is released - # Ref: https://github.com/samuelcolvin/pydantic/pull/2557 - data = response.json() - alternative_data1 = deepcopy(data) - alternative_data2 = deepcopy(data) - alternative_data1["components"]["schemas"]["Item"]["required"] = ["name", "price"] - alternative_data2["components"]["schemas"]["Item"]["required"] = [ - "name", - "price", - "tags", - ] - assert alternative_data1 == openapi_schema or alternative_data2 == openapi_schema - def test_get_item(): response = client.get("/items/next") @@ -77,3 +15,51 @@ def test_get_item(): "tags": ["breater"], "tax": None, } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + data = response.json() + assert data == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/next": { + "get": { + "summary": "Read Next Item", + "operationId": "read_next_item_items_next_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + } + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + }, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index 2d86f7b9ab..f3378fe629 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -4,136 +4,6 @@ from docs_src.dataclasses.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/authors/{author_id}/items/": { - "post": { - "summary": "Create Author Items", - "operationId": "create_author_items_authors__author_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "Author Id", "type": "string"}, - "name": "author_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Author"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/authors/": { - "get": { - "summary": "Get Authors", - "operationId": "get_authors_authors__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Get Authors Authors Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Author"}, - } - } - }, - } - }, - } - }, - }, - "components": { - "schemas": { - "Author": { - "title": "Author", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - }, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - def test_post_authors_item(): response = client.post( @@ -179,3 +49,135 @@ def test_get_authors(): ], }, ] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/authors/{author_id}/items/": { + "post": { + "summary": "Create Author Items", + "operationId": "create_author_items_authors__author_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "Author Id", "type": "string"}, + "name": "author_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Author"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/authors/": { + "get": { + "summary": "Get Authors", + "operationId": "get_authors_authors__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Get Authors Authors Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Author" + }, + } + } + }, + } + }, + } + }, + }, + "components": { + "schemas": { + "Author": { + "title": "Author", + "required": ["name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + }, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001.py index c3bca5d5b3..974b9304fd 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001.py @@ -5,132 +5,6 @@ from docs_src.dependencies.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - @pytest.mark.parametrize( "path,expected_status,expected_response", @@ -140,10 +14,151 @@ def test_openapi_schema(): ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), ("/users", 200, {"q": None, "skip": 0, "limit": 100}), - ("/openapi.json", 200, openapi_schema), ], ) def test_get(path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an.py new file mode 100644 index 0000000000..b1ca27ff8b --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_an.py @@ -0,0 +1,164 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.dependencies.tutorial001_an import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ("/items", 200, {"q": None, "skip": 0, "limit": 100}), + ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}), + ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), + ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), + ("/users", 200, {"q": None, "skip": 0, "limit": 100}), + ], +) +def test_get(path, expected_status, expected_response): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..70bed03f62 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py @@ -0,0 +1,172 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.dependencies.tutorial001_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ("/items", 200, {"q": None, "skip": 0, "limit": 100}), + ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}), + ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), + ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), + ("/users", 200, {"q": None, "skip": 0, "limit": 100}), + ], +) +def test_get(path, expected_status, expected_response, client: TestClient): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..9c5723be88 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py @@ -0,0 +1,172 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.dependencies.tutorial001_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ("/items", 200, {"q": None, "skip": 0, "limit": 100}), + ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}), + ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), + ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), + ("/users", 200, {"q": None, "skip": 0, "limit": 100}), + ], +) +def test_get(path, expected_status, expected_response, client: TestClient): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py index 32a61c8219..1bcde4e9f4 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py @@ -3,126 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -132,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 @pytest.mark.parametrize( "path,expected_status,expected_response", @@ -148,10 +21,152 @@ def test_openapi_schema(client: TestClient): ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), ("/users", 200, {"q": None, "skip": 0, "limit": 100}), - ("/openapi.json", 200, openapi_schema), ], ) def test_get(path, expected_status, expected_response, client: TestClient): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py index f2b1878d54..298bc290d2 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004.py @@ -5,90 +5,6 @@ from docs_src.dependencies.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - @pytest.mark.parametrize( "path,expected_status,expected_response", @@ -142,3 +58,95 @@ def test_get(path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an.py new file mode 100644 index 0000000000..f985be8df0 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_an.py @@ -0,0 +1,152 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.dependencies.tutorial004_an import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ( + "/items", + 200, + { + "items": [ + {"item_name": "Foo"}, + {"item_name": "Bar"}, + {"item_name": "Baz"}, + ] + }, + ), + ( + "/items?q=foo", + 200, + { + "items": [ + {"item_name": "Foo"}, + {"item_name": "Bar"}, + {"item_name": "Baz"}, + ], + "q": "foo", + }, + ), + ( + "/items?q=foo&skip=1", + 200, + {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"}, + ), + ( + "/items?q=bar&limit=2", + 200, + {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"}, + ), + ( + "/items?q=bar&skip=1&limit=1", + 200, + {"items": [{"item_name": "Bar"}], "q": "bar"}, + ), + ( + "/items?limit=1&q=bar&skip=1", + 200, + {"items": [{"item_name": "Bar"}], "q": "bar"}, + ), + ], +) +def test_get(path, expected_status, expected_response): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py new file mode 100644 index 0000000000..fc02867028 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py @@ -0,0 +1,160 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.dependencies.tutorial004_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ( + "/items", + 200, + { + "items": [ + {"item_name": "Foo"}, + {"item_name": "Bar"}, + {"item_name": "Baz"}, + ] + }, + ), + ( + "/items?q=foo", + 200, + { + "items": [ + {"item_name": "Foo"}, + {"item_name": "Bar"}, + {"item_name": "Baz"}, + ], + "q": "foo", + }, + ), + ( + "/items?q=foo&skip=1", + 200, + {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"}, + ), + ( + "/items?q=bar&limit=2", + 200, + {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"}, + ), + ( + "/items?q=bar&skip=1&limit=1", + 200, + {"items": [{"item_name": "Bar"}], "q": "bar"}, + ), + ( + "/items?limit=1&q=bar&skip=1", + 200, + {"items": [{"item_name": "Bar"}], "q": "bar"}, + ), + ], +) +def test_get(path, expected_status, expected_response, client: TestClient): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py new file mode 100644 index 0000000000..1e37673ed8 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py @@ -0,0 +1,160 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.dependencies.tutorial004_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +@pytest.mark.parametrize( + "path,expected_status,expected_response", + [ + ( + "/items", + 200, + { + "items": [ + {"item_name": "Foo"}, + {"item_name": "Bar"}, + {"item_name": "Baz"}, + ] + }, + ), + ( + "/items?q=foo", + 200, + { + "items": [ + {"item_name": "Foo"}, + {"item_name": "Bar"}, + {"item_name": "Baz"}, + ], + "q": "foo", + }, + ), + ( + "/items?q=foo&skip=1", + 200, + {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"}, + ), + ( + "/items?q=bar&limit=2", + 200, + {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"}, + ), + ( + "/items?q=bar&skip=1&limit=1", + 200, + {"items": [{"item_name": "Bar"}], "q": "bar"}, + ), + ( + "/items?limit=1&q=bar&skip=1", + 200, + {"items": [{"item_name": "Bar"}], "q": "bar"}, + ), + ], +) +def test_get(path, expected_status, expected_response, client: TestClient): + response = client.get(path) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py index e3ae0c7418..ab936ccdc0 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py @@ -3,84 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Q", "type": "string"}, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -90,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 @pytest.mark.parametrize( "path,expected_status,expected_response", @@ -150,3 +65,96 @@ def test_get(path, expected_status, expected_response, client: TestClient): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Q", "type": "string"}, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py index 2916577a2a..2e9c82d711 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py @@ -4,84 +4,6 @@ from docs_src.dependencies.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_no_headers(): response = client.get("/items/") @@ -126,3 +48,81 @@ def test_get_valid_headers(): ) assert response.status_code == 200, response.text assert response.json() == [{"item": "Foo"}, {"item": "Bar"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006_an.py b/tests/test_tutorial/test_dependencies/test_tutorial006_an.py new file mode 100644 index 0000000000..919066dcad --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial006_an.py @@ -0,0 +1,128 @@ +from fastapi.testclient import TestClient + +from docs_src.dependencies.tutorial006_an import app + +client = TestClient(app) + + +def test_get_no_headers(): + response = client.get("/items/") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-key"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +def test_get_invalid_one_header(): + response = client.get("/items/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +def test_get_invalid_second_header(): + response = client.get( + "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Key header invalid"} + + +def test_get_valid_headers(): + response = client.get( + "/items/", + headers={ + "X-Token": "fake-super-secret-token", + "X-Key": "fake-super-secret-key", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item": "Foo"}, {"item": "Bar"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py new file mode 100644 index 0000000000..c237184796 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py @@ -0,0 +1,140 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.dependencies.tutorial006_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_get_no_headers(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-key"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +@needs_py39 +def test_get_invalid_one_header(client: TestClient): + response = client.get("/items/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +@needs_py39 +def test_get_invalid_second_header(client: TestClient): + response = client.get( + "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Key header invalid"} + + +@needs_py39 +def test_get_valid_headers(client: TestClient): + response = client.get( + "/items/", + headers={ + "X-Token": "fake-super-secret-token", + "X-Key": "fake-super-secret-key", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item": "Foo"}, {"item": "Bar"}] + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py index e4e07395df..b92b96c019 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py @@ -4,120 +4,6 @@ from docs_src.dependencies.tutorial012 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_no_headers_items(): response = client.get("/items/") @@ -207,3 +93,117 @@ def test_get_valid_headers_users(): ) assert response.status_code == 200, response.text assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012_an.py b/tests/test_tutorial/test_dependencies/test_tutorial012_an.py new file mode 100644 index 0000000000..2ddb7bb532 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial012_an.py @@ -0,0 +1,209 @@ +from fastapi.testclient import TestClient + +from docs_src.dependencies.tutorial012_an import app + +client = TestClient(app) + + +def test_get_no_headers_items(): + response = client.get("/items/") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-key"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +def test_get_no_headers_users(): + response = client.get("/users/") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-key"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +def test_get_invalid_one_header_items(): + response = client.get("/items/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +def test_get_invalid_one_users(): + response = client.get("/users/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +def test_get_invalid_second_header_items(): + response = client.get( + "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Key header invalid"} + + +def test_get_invalid_second_header_users(): + response = client.get( + "/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Key header invalid"} + + +def test_get_valid_headers_items(): + response = client.get( + "/items/", + headers={ + "X-Token": "fake-super-secret-token", + "X-Key": "fake-super-secret-key", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}] + + +def test_get_valid_headers_users(): + response = client.get( + "/users/", + headers={ + "X-Token": "fake-super-secret-token", + "X-Key": "fake-super-secret-key", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py new file mode 100644 index 0000000000..595c83a531 --- /dev/null +++ b/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py @@ -0,0 +1,225 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.dependencies.tutorial012_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_get_no_headers_items(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-key"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +@needs_py39 +def test_get_no_headers_users(client: TestClient): + response = client.get("/users/") + assert response.status_code == 422, response.text + assert response.json() == { + "detail": [ + { + "loc": ["header", "x-token"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["header", "x-key"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + + +@needs_py39 +def test_get_invalid_one_header_items(client: TestClient): + response = client.get("/items/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +@needs_py39 +def test_get_invalid_one_users(client: TestClient): + response = client.get("/users/", headers={"X-Token": "invalid"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Token header invalid"} + + +@needs_py39 +def test_get_invalid_second_header_items(client: TestClient): + response = client.get( + "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Key header invalid"} + + +@needs_py39 +def test_get_invalid_second_header_users(client: TestClient): + response = client.get( + "/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "X-Key header invalid"} + + +@needs_py39 +def test_get_valid_headers_items(client: TestClient): + response = client.get( + "/items/", + headers={ + "X-Token": "fake-super-secret-token", + "X-Key": "fake-super-secret-key", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}] + + +@needs_py39 +def test_get_valid_headers_users(client: TestClient): + response = client.get( + "/users/", + headers={ + "X-Token": "fake-super-secret-token", + "X-Key": "fake-super-secret-key", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py index d52dd1a047..52f9beed5d 100644 --- a/tests/test_tutorial/test_events/test_tutorial001.py +++ b/tests/test_tutorial/test_events/test_tutorial001.py @@ -2,78 +2,84 @@ from fastapi.testclient import TestClient from docs_src.events.tutorial001 import app -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - def test_events(): with TestClient(app) as client: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema response = client.get("/items/foo") assert response.status_code == 200, response.text assert response.json() == {"name": "Fighters"} + + +def test_openapi_schema(): + with TestClient(app) as client: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py index f6ac1e07bf..882d41aa51 100644 --- a/tests/test_tutorial/test_events/test_tutorial002.py +++ b/tests/test_tutorial/test_events/test_tutorial002.py @@ -2,33 +2,35 @@ from fastapi.testclient import TestClient from docs_src.events.tutorial002 import app -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, -} - def test_events(): with TestClient(app) as client: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"name": "Foo"}] with open("log.txt") as log: assert "Application shutdown" in log.read() + + +def test_openapi_schema(): + with TestClient(app) as client: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_events/test_tutorial003.py b/tests/test_tutorial/test_events/test_tutorial003.py new file mode 100644 index 0000000000..b2820b63c6 --- /dev/null +++ b/tests/test_tutorial/test_events/test_tutorial003.py @@ -0,0 +1,92 @@ +from fastapi.testclient import TestClient + +from docs_src.events.tutorial003 import ( + app, + fake_answer_to_everything_ml_model, + ml_models, +) + + +def test_events(): + assert not ml_models, "ml_models should be empty" + with TestClient(app) as client: + assert ml_models["answer_to_everything"] == fake_answer_to_everything_ml_model + response = client.get("/predict", params={"x": 2}) + assert response.status_code == 200, response.text + assert response.json() == {"result": 84.0} + assert not ml_models, "ml_models should be empty" + + +def test_openapi_schema(): + with TestClient(app) as client: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/predict": { + "get": { + "summary": "Predict", + "operationId": "predict_predict_get", + "parameters": [ + { + "required": True, + "schema": {"title": "X", "type": "number"}, + "name": "x", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py index ec56e9ca6a..6e71bb2deb 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py @@ -4,41 +4,43 @@ from docs_src.extending_openapi.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": { - "title": "Custom title", - "version": "2.5.0", - "description": "This is a very custom OpenAPI schema", - "x-logo": {"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"}, - }, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"name": "Foo"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": { + "title": "Custom title", + "version": "2.5.0", + "description": "This is a very custom OpenAPI schema", + "x-logo": { + "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" + }, + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + } + openapi_schema = response.json() + # Request again to test the custom cache + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == openapi_schema diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py index 8522d7b9d7..07a8349903 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py @@ -5,118 +5,6 @@ from docs_src.extra_data_types.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "format": "uuid", - }, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - } - } - }, - } - } - }, - "components": { - "schemas": { - "Body_read_items_items__item_id__put": { - "title": "Body_read_items_items__item_id__put", - "type": "object", - "properties": { - "start_datetime": { - "title": "Start Datetime", - "type": "string", - "format": "date-time", - }, - "end_datetime": { - "title": "End Datetime", - "type": "string", - "format": "date-time", - }, - "repeat_at": { - "title": "Repeat At", - "type": "string", - "format": "time", - }, - "process_after": { - "title": "Process After", - "type": "number", - "format": "time-delta", - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_extra_types(): item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" data = { @@ -136,3 +24,114 @@ def test_extra_types(): response = client.put(f"/items/{item_id}", json=data) assert response.status_code == 200, response.text assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "format": "uuid", + }, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_read_items_items__item_id__put" + } + } + } + }, + } + } + }, + "components": { + "schemas": { + "Body_read_items_items__item_id__put": { + "title": "Body_read_items_items__item_id__put", + "type": "object", + "properties": { + "start_datetime": { + "title": "Start Datetime", + "type": "string", + "format": "date-time", + }, + "end_datetime": { + "title": "End Datetime", + "type": "string", + "format": "date-time", + }, + "repeat_at": { + "title": "Repeat At", + "type": "string", + "format": "time", + }, + "process_after": { + "title": "Process After", + "type": "number", + "format": "time-delta", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py new file mode 100644 index 0000000000..76836d447d --- /dev/null +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py @@ -0,0 +1,137 @@ +from fastapi.testclient import TestClient + +from docs_src.extra_data_types.tutorial001_an import app + +client = TestClient(app) + + +def test_extra_types(): + item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" + data = { + "start_datetime": "2018-12-22T14:00:00+00:00", + "end_datetime": "2018-12-24T15:00:00+00:00", + "repeat_at": "15:30:00", + "process_after": 300, + } + expected_response = data.copy() + expected_response.update( + { + "start_process": "2018-12-22T14:05:00+00:00", + "duration": 176_100, + "item_id": item_id, + } + ) + response = client.put(f"/items/{item_id}", json=data) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "format": "uuid", + }, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_read_items_items__item_id__put" + } + } + } + }, + } + } + }, + "components": { + "schemas": { + "Body_read_items_items__item_id__put": { + "title": "Body_read_items_items__item_id__put", + "type": "object", + "properties": { + "start_datetime": { + "title": "Start Datetime", + "type": "string", + "format": "date-time", + }, + "end_datetime": { + "title": "End Datetime", + "type": "string", + "format": "date-time", + }, + "repeat_at": { + "title": "Repeat At", + "type": "string", + "format": "time", + }, + "process_after": { + "title": "Process After", + "type": "number", + "format": "time-delta", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..158ee01b38 --- /dev/null +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py @@ -0,0 +1,146 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.extra_data_types.tutorial001_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_extra_types(client: TestClient): + item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" + data = { + "start_datetime": "2018-12-22T14:00:00+00:00", + "end_datetime": "2018-12-24T15:00:00+00:00", + "repeat_at": "15:30:00", + "process_after": 300, + } + expected_response = data.copy() + expected_response.update( + { + "start_process": "2018-12-22T14:05:00+00:00", + "duration": 176_100, + "item_id": item_id, + } + ) + response = client.put(f"/items/{item_id}", json=data) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "format": "uuid", + }, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_read_items_items__item_id__put" + } + } + } + }, + } + } + }, + "components": { + "schemas": { + "Body_read_items_items__item_id__put": { + "title": "Body_read_items_items__item_id__put", + "type": "object", + "properties": { + "start_datetime": { + "title": "Start Datetime", + "type": "string", + "format": "date-time", + }, + "end_datetime": { + "title": "End Datetime", + "type": "string", + "format": "date-time", + }, + "repeat_at": { + "title": "Repeat At", + "type": "string", + "format": "time", + }, + "process_after": { + "title": "Process After", + "type": "number", + "format": "time-delta", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..5be6452ee5 --- /dev/null +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py @@ -0,0 +1,146 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.extra_data_types.tutorial001_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_extra_types(client: TestClient): + item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" + data = { + "start_datetime": "2018-12-22T14:00:00+00:00", + "end_datetime": "2018-12-24T15:00:00+00:00", + "repeat_at": "15:30:00", + "process_after": 300, + } + expected_response = data.copy() + expected_response.update( + { + "start_process": "2018-12-22T14:05:00+00:00", + "duration": 176_100, + "item_id": item_id, + } + ) + response = client.put(f"/items/{item_id}", json=data) + assert response.status_code == 200, response.text + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "format": "uuid", + }, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_read_items_items__item_id__put" + } + } + } + }, + } + } + }, + "components": { + "schemas": { + "Body_read_items_items__item_id__put": { + "title": "Body_read_items_items__item_id__put", + "type": "object", + "properties": { + "start_datetime": { + "title": "Start Datetime", + "type": "string", + "format": "date-time", + }, + "end_datetime": { + "title": "End Datetime", + "type": "string", + "format": "date-time", + }, + "repeat_at": { + "title": "Repeat At", + "type": "string", + "format": "time", + }, + "process_after": { + "title": "Process After", + "type": "number", + "format": "time-delta", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py index 4efdecc53a..5413fe428b 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py @@ -3,111 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "format": "uuid", - }, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - } - } - }, - } - } - }, - "components": { - "schemas": { - "Body_read_items_items__item_id__put": { - "title": "Body_read_items_items__item_id__put", - "type": "object", - "properties": { - "start_datetime": { - "title": "Start Datetime", - "type": "string", - "format": "date-time", - }, - "end_datetime": { - "title": "End Datetime", - "type": "string", - "format": "date-time", - }, - "repeat_at": { - "title": "Repeat At", - "type": "string", - "format": "time", - }, - "process_after": { - "title": "Process After", - "type": "number", - "format": "time-delta", - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -117,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_extra_types(client: TestClient): item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" @@ -144,3 +32,115 @@ def test_extra_types(client: TestClient): response = client.put(f"/items/{item_id}", json=data) assert response.status_code == 200, response.text assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "format": "uuid", + }, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_read_items_items__item_id__put" + } + } + } + }, + } + } + }, + "components": { + "schemas": { + "Body_read_items_items__item_id__put": { + "title": "Body_read_items_items__item_id__put", + "type": "object", + "properties": { + "start_datetime": { + "title": "Start Datetime", + "type": "string", + "format": "date-time", + }, + "end_datetime": { + "title": "End Datetime", + "type": "string", + "format": "date-time", + }, + "repeat_at": { + "title": "Repeat At", + "type": "string", + "format": "time", + }, + "process_after": { + "title": "Process After", + "type": "number", + "format": "time-delta", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py index f1433470c1..f08bf4c509 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py @@ -4,107 +4,6 @@ from docs_src.extra_models.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Item Items Item Id Get", - "anyOf": [ - {"$ref": "#/components/schemas/PlaneItem"}, - {"$ref": "#/components/schemas/CarItem"}, - ], - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "PlaneItem": { - "title": "PlaneItem", - "required": ["description", "size"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "plane"}, - "size": {"title": "Size", "type": "integer"}, - }, - }, - "CarItem": { - "title": "CarItem", - "required": ["description"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "car"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_car(): response = client.get("/items/item1") @@ -123,3 +22,104 @@ def test_get_plane(): "type": "plane", "size": 5, } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Item Items Item Id Get", + "anyOf": [ + {"$ref": "#/components/schemas/PlaneItem"}, + {"$ref": "#/components/schemas/CarItem"}, + ], + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "PlaneItem": { + "title": "PlaneItem", + "required": ["description", "size"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "type": {"title": "Type", "type": "string", "default": "plane"}, + "size": {"title": "Size", "type": "integer"}, + }, + }, + "CarItem": { + "title": "CarItem", + "required": ["description"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "type": {"title": "Type", "type": "string", "default": "car"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py index 56fd83ad3a..407c717873 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py @@ -3,101 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Item Items Item Id Get", - "anyOf": [ - {"$ref": "#/components/schemas/PlaneItem"}, - {"$ref": "#/components/schemas/CarItem"}, - ], - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "PlaneItem": { - "title": "PlaneItem", - "required": ["description", "size"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "plane"}, - "size": {"title": "Size", "type": "integer"}, - }, - }, - "CarItem": { - "title": "CarItem", - "required": ["description"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "car"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -107,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_get_car(client: TestClient): response = client.get("/items/item1") @@ -133,3 +31,105 @@ def test_get_plane(client: TestClient): "type": "plane", "size": 5, } + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Item Items Item Id Get", + "anyOf": [ + {"$ref": "#/components/schemas/PlaneItem"}, + {"$ref": "#/components/schemas/CarItem"}, + ], + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "PlaneItem": { + "title": "PlaneItem", + "required": ["description", "size"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "type": {"title": "Type", "type": "string", "default": "plane"}, + "size": {"title": "Size", "type": "integer"}, + }, + }, + "CarItem": { + "title": "CarItem", + "required": ["description"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "type": {"title": "Type", "type": "string", "default": "car"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py index 548fb88340..47790ba8f9 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py @@ -4,52 +4,6 @@ from docs_src.extra_models.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "description"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_items(): response = client.get("/items/") @@ -58,3 +12,47 @@ def test_get_items(): {"name": "Foo", "description": "There comes my hero"}, {"name": "Red", "description": "It's my aeroplane"}, ] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "description"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py index 7f4f5b9beb..a98700172a 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py @@ -3,46 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "description"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - } - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -52,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_get_items(client: TestClient): response = client.get("/items/") @@ -67,3 +20,48 @@ def test_get_items(client: TestClient): {"name": "Foo", "description": "There comes my hero"}, {"name": "Red", "description": "It's my aeroplane"}, ] + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "description"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py index c3dfaa42f7..7c094b2538 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py @@ -4,41 +4,39 @@ from docs_src.extra_models.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/keyword-weights/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Keyword Weights Keyword Weights Get", - "type": "object", - "additionalProperties": {"type": "number"}, - } - } - }, - } - }, - "summary": "Read Keyword Weights", - "operationId": "read_keyword_weights_keyword_weights__get", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_items(): response = client.get("/keyword-weights/") assert response.status_code == 200, response.text assert response.json() == {"foo": 2.3, "bar": 3.4} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/keyword-weights/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Keyword Weights Keyword Weights Get", + "type": "object", + "additionalProperties": {"type": "number"}, + } + } + }, + } + }, + "summary": "Read Keyword Weights", + "operationId": "read_keyword_weights_keyword_weights__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py index 3bb5a99f10..b403864501 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py @@ -3,33 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/keyword-weights/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Keyword Weights Keyword Weights Get", - "type": "object", - "additionalProperties": {"type": "number"}, - } - } - }, - } - }, - "summary": "Read Keyword Weights", - "operationId": "read_keyword_weights_keyword_weights__get", - } - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -39,15 +12,40 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_get_items(client: TestClient): response = client.get("/keyword-weights/") assert response.status_code == 200, response.text assert response.json() == {"foo": 2.3, "bar": 3.4} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/keyword-weights/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Keyword Weights Keyword Weights Get", + "type": "object", + "additionalProperties": {"type": "number"}, + } + } + }, + } + }, + "summary": "Read Keyword Weights", + "operationId": "read_keyword_weights_keyword_weights__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001.py index 48d42285c8..ea37aec53a 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001.py @@ -5,35 +5,38 @@ from docs_src.first_steps.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Root", - "operationId": "root__get", - } - } - }, -} - @pytest.mark.parametrize( "path,expected_status,expected_response", [ ("/", 200, {"message": "Hello World"}), ("/nonexistent", 404, {"detail": "Not Found"}), - ("/openapi.json", 200, openapi_schema), ], ) def test_get_path(path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Root", + "operationId": "root__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py index 128fcea309..8b22eab9ee 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial003.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py @@ -4,166 +4,6 @@ from docs_src.generate_clients.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "tags": ["items"], - "summary": "Get Items", - "operationId": "items-get_items", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Items-Get Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - } - }, - }, - "post": { - "tags": ["items"], - "summary": "Create Item", - "operationId": "items-create_item", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/users/": { - "post": { - "tags": ["users"], - "summary": "Create User", - "operationId": "users-create_user", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "ResponseMessage": { - "title": "ResponseMessage", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "User": { - "title": "User", - "required": ["username", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi(): - with client: - response = client.get("/openapi.json") - - assert response.json() == openapi_schema - def test_post_items(): response = client.post("/items/", json={"name": "Foo", "price": 5}) @@ -186,3 +26,162 @@ def test_get_items(): {"name": "Plumbus", "price": 3}, {"name": "Portal Gun", "price": 9001}, ] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "items-get_items", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Items-Get Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + } + }, + }, + "post": { + "tags": ["items"], + "summary": "Create Item", + "operationId": "items-create_item", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/users/": { + "post": { + "tags": ["users"], + "summary": "Create User", + "operationId": "users-create_user", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "ResponseMessage": { + "title": "ResponseMessage", + "required": ["message"], + "type": "object", + "properties": {"message": {"title": "Message", "type": "string"}}, + }, + "User": { + "title": "User", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py index ffd79ccff3..99a1053cab 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py @@ -4,78 +4,6 @@ from docs_src.handling_errors.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_item(): response = client.get("/items/foo") @@ -88,3 +16,75 @@ def test_get_item_not_found(): assert response.status_code == 404, response.text assert response.headers.get("x-error") is None assert response.json() == {"detail": "Item not found"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py index e678499c63..091c74f4de 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py @@ -4,78 +4,6 @@ from docs_src.handling_errors.tutorial002 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items-header/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Header", - "operationId": "read_item_header_items_header__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_item_header(): response = client.get("/items-header/foo") @@ -88,3 +16,75 @@ def test_get_item_not_found_header(): assert response.status_code == 404, response.text assert response.headers.get("x-error") == "There goes my error" assert response.json() == {"detail": "Item not found"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items-header/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Header", + "operationId": "read_item_header_items_header__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py index a01726dc2d..1639cb1d8b 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py @@ -4,78 +4,6 @@ from docs_src.handling_errors.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/unicorns/{name}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Unicorn", - "operationId": "read_unicorn_unicorns__name__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Name", "type": "string"}, - "name": "name", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get(): response = client.get("/unicorns/shinny") @@ -89,3 +17,75 @@ def test_get_exception(): assert response.json() == { "message": "Oops! yolo did something. There goes a rainbow..." } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/unicorns/{name}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Unicorn", + "operationId": "read_unicorn_unicorns__name__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Name", "type": "string"}, + "name": "name", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py index 0b5f747986..246f3b94c1 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py @@ -4,78 +4,6 @@ from docs_src.handling_errors.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_validation_error(): response = client.get("/items/foo") @@ -98,3 +26,75 @@ def test_get(): response = client.get("/items/2") assert response.status_code == 200, response.text assert response.json() == {"item_id": 2} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py index 253f3d006a..af47fd1a4f 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py @@ -4,87 +4,6 @@ from docs_src.handling_errors.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["title", "size"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "size": {"title": "Size", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post_validation_error(): response = client.post("/items/", json={"title": "towel", "size": "XL"}) @@ -106,3 +25,84 @@ def test_post(): response = client.post("/items/", json=data) assert response.status_code == 200, response.text assert response.json() == data + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["title", "size"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "size": {"title": "Size", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py index 21233d7bbf..4a39bd1022 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py @@ -4,78 +4,6 @@ from docs_src.handling_errors.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_validation_error(): response = client.get("/items/foo") @@ -101,3 +29,75 @@ def test_get(): response = client.get("/items/2") assert response.status_code == 200, response.text assert response.json() == {"item_id": 2} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py index 273cf3249e..80f502d6a8 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001.py @@ -6,77 +6,9 @@ from docs_src.header_params.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "User-Agent", "type": "string"}, - "name": "user-agent", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - @pytest.mark.parametrize( "path,headers,expected_status,expected_response", [ - ("/openapi.json", None, 200, openapi_schema), ("/items", None, 200, {"User-Agent": "testclient"}), ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}), ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), @@ -86,3 +18,75 @@ def test(path, headers, expected_status, expected_response): response = client.get(path, headers=headers) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "User-Agent", "type": "string"}, + "name": "user-agent", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_an.py b/tests/test_tutorial/test_header_params/test_tutorial001_an.py new file mode 100644 index 0000000000..f0ad7b8160 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial001_an.py @@ -0,0 +1,92 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.header_params.tutorial001_an import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"User-Agent": "testclient"}), + ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}), + ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), + ], +) +def test(path, headers, expected_status, expected_response): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "User-Agent", "type": "string"}, + "name": "user-agent", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..d095c71233 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py @@ -0,0 +1,100 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.header_params.tutorial001_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"User-Agent": "testclient"}), + ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}), + ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), + ], +) +def test(path, headers, expected_status, expected_response, client: TestClient): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "User-Agent", "type": "string"}, + "name": "user-agent", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py index 77a60eb9db..bf176bba27 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py @@ -3,72 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "User-Agent", "type": "string"}, - "name": "user-agent", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -82,7 +16,6 @@ def get_client(): @pytest.mark.parametrize( "path,headers,expected_status,expected_response", [ - ("/openapi.json", None, 200, openapi_schema), ("/items", None, 200, {"User-Agent": "testclient"}), ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}), ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), @@ -92,3 +25,76 @@ def test(path, headers, expected_status, expected_response, client: TestClient): response = client.get(path, headers=headers) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "User-Agent", "type": "string"}, + "name": "user-agent", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py new file mode 100644 index 0000000000..516abda8bf --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial002.py @@ -0,0 +1,103 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.header_params.tutorial002 import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"strange_header": None}), + ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), + ( + "/items", + {"strange_header": "FastAPI test"}, + 200, + {"strange_header": "FastAPI test"}, + ), + ( + "/items", + {"strange-header": "Not really underscore"}, + 200, + {"strange_header": None}, + ), + ], +) +def test(path, headers, expected_status, expected_response): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Strange Header", "type": "string"}, + "name": "strange_header", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_an.py b/tests/test_tutorial/test_header_params/test_tutorial002_an.py new file mode 100644 index 0000000000..97493e6040 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial002_an.py @@ -0,0 +1,103 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.header_params.tutorial002_an import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"strange_header": None}), + ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), + ( + "/items", + {"strange_header": "FastAPI test"}, + 200, + {"strange_header": "FastAPI test"}, + ), + ( + "/items", + {"strange-header": "Not really underscore"}, + 200, + {"strange_header": None}, + ), + ], +) +def test(path, headers, expected_status, expected_response): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Strange Header", "type": "string"}, + "name": "strange_header", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py b/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py new file mode 100644 index 0000000000..e0c60342a7 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py @@ -0,0 +1,111 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.header_params.tutorial002_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"strange_header": None}), + ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), + ( + "/items", + {"strange_header": "FastAPI test"}, + 200, + {"strange_header": "FastAPI test"}, + ), + ( + "/items", + {"strange-header": "Not really underscore"}, + 200, + {"strange_header": None}, + ), + ], +) +def test(path, headers, expected_status, expected_response, client: TestClient): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Strange Header", "type": "string"}, + "name": "strange_header", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py b/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py new file mode 100644 index 0000000000..c1bc5faf8a --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py @@ -0,0 +1,114 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.header_params.tutorial002_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"strange_header": None}), + ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), + ( + "/items", + {"strange_header": "FastAPI test"}, + 200, + {"strange_header": "FastAPI test"}, + ), + ( + "/items", + {"strange-header": "Not really underscore"}, + 200, + {"strange_header": None}, + ), + ], +) +def test(path, headers, expected_status, expected_response, client: TestClient): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(): + from docs_src.header_params.tutorial002_an_py39 import app + + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Strange Header", "type": "string"}, + "name": "strange_header", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_py310.py b/tests/test_tutorial/test_header_params/test_tutorial002_py310.py new file mode 100644 index 0000000000..81871b8c6d --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial002_py310.py @@ -0,0 +1,114 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.header_params.tutorial002_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"strange_header": None}), + ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), + ( + "/items", + {"strange_header": "FastAPI test"}, + 200, + {"strange_header": "FastAPI test"}, + ), + ( + "/items", + {"strange-header": "Not really underscore"}, + 200, + {"strange_header": None}, + ), + ], +) +def test(path, headers, expected_status, expected_response, client: TestClient): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(): + from docs_src.header_params.tutorial002_py310 import app + + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": {"title": "Strange Header", "type": "string"}, + "name": "strange_header", + "in": "header", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py new file mode 100644 index 0000000000..99dd9e25fe --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial003.py @@ -0,0 +1,98 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.header_params.tutorial003 import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"X-Token values": None}), + ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), + # TODO: fix this, is it a bug? + # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), + ], +) +def test(path, headers, expected_status, expected_response): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "X-Token", + "type": "array", + "items": {"type": "string"}, + }, + "name": "x-token", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_an.py b/tests/test_tutorial/test_header_params/test_tutorial003_an.py new file mode 100644 index 0000000000..4477da7a89 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial003_an.py @@ -0,0 +1,98 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.header_params.tutorial003_an import app + +client = TestClient(app) + + +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"X-Token values": None}), + ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), + # TODO: fix this, is it a bug? + # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), + ], +) +def test(path, headers, expected_status, expected_response): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "X-Token", + "type": "array", + "items": {"type": "string"}, + }, + "name": "x-token", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py b/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py new file mode 100644 index 0000000000..b52304a2b9 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py @@ -0,0 +1,106 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.header_params.tutorial003_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"X-Token values": None}), + ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), + # TODO: fix this, is it a bug? + # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), + ], +) +def test(path, headers, expected_status, expected_response, client: TestClient): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200 + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "X-Token", + "type": "array", + "items": {"type": "string"}, + }, + "name": "x-token", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py b/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py new file mode 100644 index 0000000000..dffdd16228 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py @@ -0,0 +1,106 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.header_params.tutorial003_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"X-Token values": None}), + ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), + # TODO: fix this, is it a bug? + # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), + ], +) +def test(path, headers, expected_status, expected_response, client: TestClient): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200 + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "X-Token", + "type": "array", + "items": {"type": "string"}, + }, + "name": "x-token", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_py310.py b/tests/test_tutorial/test_header_params/test_tutorial003_py310.py new file mode 100644 index 0000000000..64ef7b22a2 --- /dev/null +++ b/tests/test_tutorial/test_header_params/test_tutorial003_py310.py @@ -0,0 +1,106 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.header_params.tutorial003_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +@pytest.mark.parametrize( + "path,headers,expected_status,expected_response", + [ + ("/items", None, 200, {"X-Token values": None}), + ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), + # TODO: fix this, is it a bug? + # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), + ], +) +def test(path, headers, expected_status, expected_response, client: TestClient): + response = client.get(path, headers=headers) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200 + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "X-Token", + "type": "array", + "items": {"type": "string"}, + }, + "name": "x-token", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_metadata/test_tutorial001.py b/tests/test_tutorial/test_metadata/test_tutorial001.py index b7281e2935..f1ddc32595 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001.py @@ -4,47 +4,45 @@ from docs_src.metadata.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": { - "title": "ChimichangApp", - "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", - "termsOfService": "http://example.com/terms/", - "contact": { - "name": "Deadpoolio the Amazing", - "url": "http://x-force.example.com/contact/", - "email": "dp@x-force.example.com", - }, - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html", - }, - "version": "0.0.1", - }, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_items(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"name": "Katana"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": { + "title": "ChimichangApp", + "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", + "termsOfService": "http://example.com/terms/", + "contact": { + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + }, + "version": "0.0.1", + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_metadata/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial004.py index 2d255b8b0d..f7f47a558e 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial004.py +++ b/tests/test_tutorial/test_metadata/test_tutorial004.py @@ -4,62 +4,60 @@ from docs_src.metadata.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "tags": ["users"], - "summary": "Get Users", - "operationId": "get_users_users__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/items/": { - "get": { - "tags": ["items"], - "summary": "Get Items", - "operationId": "get_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - }, - "tags": [ - { - "name": "users", - "description": "Operations with users. The **login** logic is also here.", - }, - { - "name": "items", - "description": "Manage items. So _fancy_ they have their own docs.", - "externalDocs": { - "description": "Items external docs", - "url": "https://fastapi.tiangolo.com/", - }, - }, - ], -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_path_operations(): response = client.get("/items/") assert response.status_code == 200, response.text response = client.get("/users/") assert response.status_code == 200, response.text + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "tags": ["users"], + "summary": "Get Users", + "operationId": "get_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "get_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + }, + "tags": [ + { + "name": "users", + "description": "Operations with users. The **login** logic is also here.", + }, + { + "name": "items", + "description": "Manage items. So _fancy_ they have their own docs.", + "externalDocs": { + "description": "Items external docs", + "url": "https://fastapi.tiangolo.com/", + }, + }, + ], + } diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py index e773e7f8f5..c6cdc60648 100644 --- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py @@ -4,162 +4,6 @@ from docs_src.openapi_callbacks.tutorial001 import app, invoice_notification client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/invoices/": { - "post": { - "summary": "Create Invoice", - "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', - "operationId": "create_invoice_invoices__post", - "parameters": [ - { - "required": False, - "schema": { - "title": "Callback Url", - "maxLength": 2083, - "minLength": 1, - "type": "string", - "format": "uri", - }, - "name": "callback_url", - "in": "query", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Invoice"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "callbacks": { - "invoice_notification": { - "{$callback_url}/invoices/{$request.body.id}": { - "post": { - "summary": "Invoice Notification", - "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEvent" - } - } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEventReceived" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Invoice": { - "title": "Invoice", - "required": ["id", "customer", "total"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - "customer": {"title": "Customer", "type": "string"}, - "total": {"title": "Total", "type": "number"}, - }, - }, - "InvoiceEvent": { - "title": "InvoiceEvent", - "required": ["description", "paid"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "paid": {"title": "Paid", "type": "boolean"}, - }, - }, - "InvoiceEventReceived": { - "title": "InvoiceEventReceived", - "required": ["ok"], - "type": "object", - "properties": {"ok": {"title": "Ok", "type": "boolean"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi(): - with client: - response = client.get("/openapi.json") - - assert response.json() == openapi_schema - def test_get(): response = client.post( @@ -172,3 +16,158 @@ def test_get(): def test_dummy_callback(): # Just for coverage invoice_notification({}) + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/invoices/": { + "post": { + "summary": "Create Invoice", + "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', + "operationId": "create_invoice_invoices__post", + "parameters": [ + { + "required": False, + "schema": { + "title": "Callback Url", + "maxLength": 2083, + "minLength": 1, + "type": "string", + "format": "uri", + }, + "name": "callback_url", + "in": "query", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Invoice"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "callbacks": { + "invoice_notification": { + "{$callback_url}/invoices/{$request.body.id}": { + "post": { + "summary": "Invoice Notification", + "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEvent" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEventReceived" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + } + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Invoice": { + "title": "Invoice", + "required": ["id", "customer", "total"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + "customer": {"title": "Customer", "type": "string"}, + "total": {"title": "Total", "type": "number"}, + }, + }, + "InvoiceEvent": { + "title": "InvoiceEvent", + "required": ["description", "paid"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "paid": {"title": "Paid", "type": "boolean"}, + }, + }, + "InvoiceEventReceived": { + "title": "InvoiceEventReceived", + "required": ["ok"], + "type": "object", + "properties": {"ok": {"title": "Ok", "type": "boolean"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py index 3b5301348f..c1cdbee241 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py @@ -4,33 +4,31 @@ from docs_src.path_operation_advanced_configuration.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "some_specific_id_you_define", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "some_specific_id_you_define", + } + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py index 01acb664c7..fdaddd0187 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py @@ -4,33 +4,31 @@ from docs_src.path_operation_advanced_configuration.tutorial002 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items", - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items", + } + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py index 4a23db7bc1..782c64a845 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py @@ -4,20 +4,18 @@ from docs_src.path_operation_advanced_configuration.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": {}, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": {}, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index 456e509d5b..f5fd868eb7 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -4,101 +4,6 @@ from docs_src.path_operation_advanced_configuration.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item\n", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_query_params_str_validations(): response = client.post("/items/", json={"name": "Foo", "price": 42}) @@ -110,3 +15,98 @@ def test_query_params_str_validations(): "tax": None, "tags": [], } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create an item", + "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + "tags": { + "title": "Tags", + "uniqueItems": True, + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py index 5042d18375..52379c01e1 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py @@ -4,33 +4,31 @@ from docs_src.path_operation_advanced_configuration.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "x-aperture-labs-portal": "blue", - } - } - }, -} + +def test_get(): + response = client.get("/items/") + assert response.status_code == 200, response.text def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_get(): - response = client.get("/items/") - assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "x-aperture-labs-portal": "blue", + } + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py index 5533b29571..deb6b09101 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py @@ -4,50 +4,9 @@ from docs_src.path_operation_advanced_configuration.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"type": "string"}, - "price": {"type": "number"}, - "description": {"type": "string"}, - }, - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post(): - response = client.post("/items/", data=b"this is actually not validated") + response = client.post("/items/", content=b"this is actually not validated") assert response.status_code == 200, response.text assert response.json() == { "size": 30, @@ -57,3 +16,42 @@ def test_post(): "description": "Just kiddin', no magic here. ✨", }, } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"type": "string"}, + "price": {"type": "number"}, + "description": {"type": "string"}, + }, + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py index cb5dbc8eb0..470956a77d 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py @@ -4,51 +4,6 @@ from docs_src.path_operation_advanced_configuration.tutorial007 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/x-yaml": { - "schema": { - "title": "Item", - "required": ["name", "tags"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - }, - }, - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post(): yaml_data = """ @@ -58,7 +13,7 @@ def test_post(): - x-men - x-avengers """ - response = client.post("/items/", data=yaml_data) + response = client.post("/items/", content=yaml_data) assert response.status_code == 200, response.text assert response.json() == { "name": "Deadpoolio", @@ -74,7 +29,7 @@ def test_post_broken_yaml(): x - x-men x - x-avengers """ - response = client.post("/items/", data=yaml_data) + response = client.post("/items/", content=yaml_data) assert response.status_code == 422, response.text assert response.json() == {"detail": "Invalid YAML"} @@ -88,10 +43,53 @@ def test_post_invalid(): - x-avengers - sneaky: object """ - response = client.post("/items/", data=yaml_data) + response = client.post("/items/", content=yaml_data) assert response.status_code == 422, response.text assert response.json() == { "detail": [ {"loc": ["tags", 3], "msg": "str type expected", "type": "type_error.str"} ] } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/x-yaml": { + "schema": { + "title": "Item", + "required": ["name", "tags"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + }, + }, + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py index be9f2afecf..76e44b5e59 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py @@ -4,45 +4,6 @@ from docs_src.path_operation_configuration.tutorial002b import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "tags": ["items"], - "summary": "Get Items", - "operationId": "get_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/users/": { - "get": { - "tags": ["users"], - "summary": "Read Users", - "operationId": "read_users_users__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_get_items(): response = client.get("/items/") @@ -54,3 +15,40 @@ def test_get_users(): response = client.get("/users/") assert response.status_code == 200, response.text assert response.json() == ["Rick", "Morty"] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "get_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/users/": { + "get": { + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py index e587519a00..cf8e203a0e 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -4,101 +4,6 @@ from docs_src.path_operation_configuration.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_query_params_str_validations(): response = client.post("/items/", json={"name": "Foo", "price": 42}) @@ -110,3 +15,98 @@ def test_query_params_str_validations(): "tax": None, "tags": [], } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "The created item", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create an item", + "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + "tags": { + "title": "Tags", + "uniqueItems": True, + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py index 43a7a610de..497fc90242 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py @@ -3,95 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -101,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_query_params_str_validations(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": 42}) @@ -119,3 +23,99 @@ def test_query_params_str_validations(client: TestClient): "tax": None, "tags": [], } + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "The created item", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create an item", + "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + "tags": { + "title": "Tags", + "uniqueItems": True, + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py index 62aa73ac52..09fac44c4d 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py @@ -3,95 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -101,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_query_params_str_validations(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": 42}) @@ -119,3 +23,99 @@ def test_query_params_str_validations(client: TestClient): "tax": None, "tags": [], } + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "The created item", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create an item", + "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number"}, + "tags": { + "title": "Tags", + "uniqueItems": True, + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py index 582caed44a..e90771f24b 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py @@ -5,59 +5,6 @@ from docs_src.path_operation_configuration.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "tags": ["items"], - "summary": "Read Items", - "operationId": "read_items_items__get", - } - }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "tags": ["users"], - "summary": "Read Users", - "operationId": "read_users_users__get", - } - }, - "/elements/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "tags": ["items"], - "summary": "Read Elements", - "operationId": "read_elements_elements__get", - "deprecated": True, - } - }, - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - @pytest.mark.parametrize( "path,expected_status,expected_response", @@ -71,3 +18,54 @@ def test_query_params_str_validations(path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "tags": ["items"], + "summary": "Read Items", + "operationId": "read_items_items__get", + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + } + }, + "/elements/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "tags": ["items"], + "summary": "Read Elements", + "operationId": "read_elements_elements__get", + "deprecated": True, + } + }, + }, + } diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py index 7f0227ecfb..ab0455bf5d 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial004.py +++ b/tests/test_tutorial/test_path_params/test_tutorial004.py @@ -4,78 +4,6 @@ from docs_src.path_params.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/{file_path}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read File", - "operationId": "read_file_files__file_path__get", - "parameters": [ - { - "required": True, - "schema": {"title": "File Path", "type": "string"}, - "name": "file_path", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_file_path(): response = client.get("/files/home/johndoe/myfile.txt") @@ -89,3 +17,75 @@ def test_root_file_path(): print(response.content) assert response.status_code == 200, response.text assert response.json() == {"file_path": "/home/johndoe/myfile.txt"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/{file_path}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read File", + "operationId": "read_file_files__file_path__get", + "parameters": [ + { + "required": True, + "schema": {"title": "File Path", "type": "string"}, + "name": "file_path", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py index eae3637bed..3401f22534 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params/test_tutorial005.py @@ -5,156 +5,6 @@ from docs_src.path_params.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/models/{model_name}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Model", - "operationId": "get_model_models__model_name__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Model Name", - "enum": ["alexnet", "resnet", "lenet"], - "type": "string", - }, - "name": "model_name", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -openapi_schema2 = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/models/{model_name}": { - "get": { - "summary": "Get Model", - "operationId": "get_model_models__model_name__get", - "parameters": [ - { - "required": True, - "schema": {"$ref": "#/components/schemas/ModelName"}, - "name": "model_name", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ModelName": { - "title": "ModelName", - "enum": ["alexnet", "resnet", "lenet"], - "type": "string", - "description": "An enumeration.", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - data = response.json() - assert data == openapi_schema or data == openapi_schema2 - @pytest.mark.parametrize( "url,status_code,expected", @@ -194,3 +44,82 @@ def test_get_enums(url, status_code, expected): response = client.get(url) assert response.status_code == status_code assert response.json() == expected + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + data = response.json() + assert data == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/models/{model_name}": { + "get": { + "summary": "Get Model", + "operationId": "get_model_models__model_name__get", + "parameters": [ + { + "required": True, + "schema": {"$ref": "#/components/schemas/ModelName"}, + "name": "model_name", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ModelName": { + "title": "ModelName", + "enum": ["alexnet", "resnet", "lenet"], + "type": "string", + "description": "An enumeration.", + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py index 07178f8a6e..3c408449b8 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params/test_tutorial005.py @@ -5,78 +5,6 @@ from docs_src.query_params.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User Item", - "operationId": "read_user_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Needy", "type": "string"}, - "name": "needy", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - query_required = { "detail": [ @@ -92,7 +20,6 @@ query_required = { @pytest.mark.parametrize( "path,expected_status,expected_response", [ - ("/openapi.json", 200, openapi_schema), ("/items/foo?needy=very", 200, {"item_id": "foo", "needy": "very"}), ("/items/foo", 422, query_required), ("/items/foo", 422, query_required), @@ -102,3 +29,81 @@ def test(path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User Item", + "operationId": "read_user_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Needy", "type": "string"}, + "name": "needy", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py index 73c5302e7c..7fe58a9900 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006.py @@ -5,90 +5,6 @@ from docs_src.query_params.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User Item", - "operationId": "read_user_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Needy", "type": "string"}, - "name": "needy", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer"}, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - query_required = { "detail": [ @@ -104,7 +20,6 @@ query_required = { @pytest.mark.parametrize( "path,expected_status,expected_response", [ - ("/openapi.json", 200, openapi_schema), ( "/items/foo?needy=very", 200, @@ -139,3 +54,97 @@ def test(path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User Item", + "operationId": "read_user_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Needy", "type": "string"}, + "name": "needy", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": {"title": "Limit", "type": "integer"}, + "name": "limit", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py index 141525f15f..b90c0a6c8e 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py @@ -3,91 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User Item", - "operationId": "read_user_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Needy", "type": "string"}, - "name": "needy", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer"}, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - query_required = { "detail": [ { @@ -111,7 +26,6 @@ def get_client(): @pytest.mark.parametrize( "path,expected_status,expected_response", [ - ("/openapi.json", 200, openapi_schema), ( "/items/foo?needy=very", 200, @@ -146,3 +60,98 @@ def test(path, expected_status, expected_response, client: TestClient): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User Item", + "operationId": "read_user_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Needy", "type": "string"}, + "name": "needy", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": {"title": "Limit", "type": "integer"}, + "name": "limit", + "in": "query", + }, + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py deleted file mode 100644 index f8d7f85c8e..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py +++ /dev/null @@ -1,122 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial010 import app - -client = TestClient(app) - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - }, - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -regex_error = { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] -} - - -@pytest.mark.parametrize( - "q_name,q,expected_status,expected_response", - [ - (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), - ( - "item-query", - "fixedquery", - 200, - {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"}, - ), - ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), - ("item-query", "nonregexquery", 422, regex_error), - ], -) -def test_query_params_str_validations(q_name, q, expected_status, expected_response): - url = "/items/" - if q_name and q: - url = f"{url}?{q_name}={q}" - response = client.get(url) - assert response.status_code == expected_status - assert response.json() == expected_response diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py deleted file mode 100644 index 298b5d616c..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py +++ /dev/null @@ -1,132 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - }, - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -regex_error = { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] -} - - -@needs_py310 -@pytest.mark.parametrize( - "q_name,q,expected_status,expected_response", - [ - (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), - ( - "item-query", - "fixedquery", - 200, - {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"}, - ), - ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), - ("item-query", "nonregexquery", 422, regex_error), - ], -) -def test_query_params_str_validations( - q_name, q, expected_status, expected_response, client: TestClient -): - url = "/items/" - if q_name and q: - url = f"{url}?{q_name}={q}" - response = client.get(url) - assert response.status_code == expected_status - assert response.json() == expected_response diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py new file mode 100644 index 0000000000..c41f554ba3 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -0,0 +1,122 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.query_params_str_validations.tutorial010 import app + +client = TestClient(app) + + +regex_error = { + "detail": [ + { + "ctx": {"pattern": "^fixedquery$"}, + "loc": ["query", "item-query"], + "msg": 'string does not match regex "^fixedquery$"', + "type": "value_error.str.regex", + } + ] +} + + +@pytest.mark.parametrize( + "q_name,q,expected_status,expected_response", + [ + (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ( + "item-query", + "fixedquery", + 200, + {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"}, + ), + ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ("item-query", "nonregexquery", 422, regex_error), + ], +) +def test_query_params_str_validations(q_name, q, expected_status, expected_response): + url = "/items/" + if q_name and q: + url = f"{url}?{q_name}={q}" + response = client.get(url) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "description": "Query string for the items to search in the database that have a good match", + "required": False, + "deprecated": True, + "schema": { + "title": "Query string", + "maxLength": 50, + "minLength": 3, + "pattern": "^fixedquery$", + "type": "string", + "description": "Query string for the items to search in the database that have a good match", + }, + "name": "item-query", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py new file mode 100644 index 0000000000..dc8028f811 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py @@ -0,0 +1,122 @@ +import pytest +from fastapi.testclient import TestClient + +from docs_src.query_params_str_validations.tutorial010_an import app + +client = TestClient(app) + + +regex_error = { + "detail": [ + { + "ctx": {"pattern": "^fixedquery$"}, + "loc": ["query", "item-query"], + "msg": 'string does not match regex "^fixedquery$"', + "type": "value_error.str.regex", + } + ] +} + + +@pytest.mark.parametrize( + "q_name,q,expected_status,expected_response", + [ + (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ( + "item-query", + "fixedquery", + 200, + {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"}, + ), + ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ("item-query", "nonregexquery", 422, regex_error), + ], +) +def test_query_params_str_validations(q_name, q, expected_status, expected_response): + url = "/items/" + if q_name and q: + url = f"{url}?{q_name}={q}" + response = client.get(url) + assert response.status_code == expected_status + assert response.json() == expected_response + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "description": "Query string for the items to search in the database that have a good match", + "required": False, + "deprecated": True, + "schema": { + "title": "Query string", + "maxLength": 50, + "minLength": 3, + "pattern": "^fixedquery$", + "type": "string", + "description": "Query string for the items to search in the database that have a good match", + }, + "name": "item-query", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py new file mode 100644 index 0000000000..496b95b797 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py @@ -0,0 +1,132 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial010_an_py310 import app + + client = TestClient(app) + return client + + +regex_error = { + "detail": [ + { + "ctx": {"pattern": "^fixedquery$"}, + "loc": ["query", "item-query"], + "msg": 'string does not match regex "^fixedquery$"', + "type": "value_error.str.regex", + } + ] +} + + +@needs_py310 +@pytest.mark.parametrize( + "q_name,q,expected_status,expected_response", + [ + (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ( + "item-query", + "fixedquery", + 200, + {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"}, + ), + ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ("item-query", "nonregexquery", 422, regex_error), + ], +) +def test_query_params_str_validations( + q_name, q, expected_status, expected_response, client: TestClient +): + url = "/items/" + if q_name and q: + url = f"{url}?{q_name}={q}" + response = client.get(url) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "description": "Query string for the items to search in the database that have a good match", + "required": False, + "deprecated": True, + "schema": { + "title": "Query string", + "maxLength": 50, + "minLength": 3, + "pattern": "^fixedquery$", + "type": "string", + "description": "Query string for the items to search in the database that have a good match", + }, + "name": "item-query", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py new file mode 100644 index 0000000000..2005e50433 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py @@ -0,0 +1,132 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial010_an_py39 import app + + client = TestClient(app) + return client + + +regex_error = { + "detail": [ + { + "ctx": {"pattern": "^fixedquery$"}, + "loc": ["query", "item-query"], + "msg": 'string does not match regex "^fixedquery$"', + "type": "value_error.str.regex", + } + ] +} + + +@needs_py39 +@pytest.mark.parametrize( + "q_name,q,expected_status,expected_response", + [ + (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ( + "item-query", + "fixedquery", + 200, + {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"}, + ), + ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ("item-query", "nonregexquery", 422, regex_error), + ], +) +def test_query_params_str_validations( + q_name, q, expected_status, expected_response, client: TestClient +): + url = "/items/" + if q_name and q: + url = f"{url}?{q_name}={q}" + response = client.get(url) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "description": "Query string for the items to search in the database that have a good match", + "required": False, + "deprecated": True, + "schema": { + "title": "Query string", + "maxLength": 50, + "minLength": 3, + "pattern": "^fixedquery$", + "type": "string", + "description": "Query string for the items to search in the database that have a good match", + }, + "name": "item-query", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py new file mode 100644 index 0000000000..8147d768e0 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py @@ -0,0 +1,132 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial010_py310 import app + + client = TestClient(app) + return client + + +regex_error = { + "detail": [ + { + "ctx": {"pattern": "^fixedquery$"}, + "loc": ["query", "item-query"], + "msg": 'string does not match regex "^fixedquery$"', + "type": "value_error.str.regex", + } + ] +} + + +@needs_py310 +@pytest.mark.parametrize( + "q_name,q,expected_status,expected_response", + [ + (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ( + "item-query", + "fixedquery", + 200, + {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"}, + ), + ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}), + ("item-query", "nonregexquery", 422, regex_error), + ], +) +def test_query_params_str_validations( + q_name, q, expected_status, expected_response, client: TestClient +): + url = "/items/" + if q_name and q: + url = f"{url}?{q_name}={q}" + response = client.get(url) + assert response.status_code == expected_status + assert response.json() == expected_response + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "description": "Query string for the items to search in the database that have a good match", + "required": False, + "deprecated": True, + "schema": { + "title": "Query string", + "maxLength": 50, + "minLength": 3, + "pattern": "^fixedquery$", + "type": "string", + "description": "Query string for the items to search in the database that have a good match", + }, + "name": "item-query", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index ad3645f314..d6d69c1691 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -4,82 +4,6 @@ from docs_src.query_params_str_validations.tutorial011 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_multi_query_values(): url = "/items/?q=foo&q=bar" @@ -93,3 +17,79 @@ def test_query_no_values(): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": None} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py new file mode 100644 index 0000000000..3a53d422e0 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py @@ -0,0 +1,95 @@ +from fastapi.testclient import TestClient + +from docs_src.query_params_str_validations.tutorial011_an import app + +client = TestClient(app) + + +def test_multi_query_values(): + url = "/items/?q=foo&q=bar" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["foo", "bar"]} + + +def test_query_no_values(): + url = "/items/" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": None} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py new file mode 100644 index 0000000000..f00df28791 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py @@ -0,0 +1,105 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial011_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_multi_query_values(client: TestClient): + url = "/items/?q=foo&q=bar" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["foo", "bar"]} + + +@needs_py310 +def test_query_no_values(client: TestClient): + url = "/items/" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": None} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py new file mode 100644 index 0000000000..895fb8e9f4 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py @@ -0,0 +1,105 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial011_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_multi_query_values(client: TestClient): + url = "/items/?q=foo&q=bar" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["foo", "bar"]} + + +@needs_py39 +def test_query_no_values(client: TestClient): + url = "/items/" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": None} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py index 9330037edd..4f4b1fd558 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py @@ -3,76 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -82,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" @@ -103,3 +26,80 @@ def test_query_no_values(client: TestClient): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": None} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py index 11f23be27c..d85bb62312 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py @@ -3,76 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -82,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" @@ -103,3 +26,80 @@ def test_query_no_values(client: TestClient): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": None} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index d69139dda5..7bc020540a 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -4,83 +4,6 @@ from docs_src.query_params_str_validations.tutorial012 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_default_query_values(): url = "/items/" @@ -94,3 +17,80 @@ def test_multi_query_values(): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["baz", "foobar"]} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + "default": ["foo", "bar"], + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py new file mode 100644 index 0000000000..be5557f6ad --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py @@ -0,0 +1,96 @@ +from fastapi.testclient import TestClient + +from docs_src.query_params_str_validations.tutorial012_an import app + +client = TestClient(app) + + +def test_default_query_values(): + url = "/items/" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["foo", "bar"]} + + +def test_multi_query_values(): + url = "/items/?q=baz&q=foobar" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["baz", "foobar"]} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + "default": ["foo", "bar"], + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py new file mode 100644 index 0000000000..d9512e193f --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py @@ -0,0 +1,106 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial012_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_default_query_values(client: TestClient): + url = "/items/" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["foo", "bar"]} + + +@needs_py39 +def test_multi_query_values(client: TestClient): + url = "/items/?q=baz&q=foobar" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["baz", "foobar"]} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + "default": ["foo", "bar"], + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py index b25bb2847f..b2a2c8d1d1 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py @@ -3,77 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -83,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_default_query_values(client: TestClient): url = "/items/" @@ -104,3 +26,81 @@ def test_multi_query_values(client: TestClient): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["baz", "foobar"]} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + "default": ["foo", "bar"], + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index 1b2e363540..4a0b9e8b5f 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -4,83 +4,6 @@ from docs_src.query_params_str_validations.tutorial013 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_multi_query_values(): url = "/items/?q=foo&q=bar" @@ -94,3 +17,80 @@ def test_query_no_values(): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": []} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {}, + "default": [], + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py new file mode 100644 index 0000000000..71e4638ae4 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py @@ -0,0 +1,96 @@ +from fastapi.testclient import TestClient + +from docs_src.query_params_str_validations.tutorial013_an import app + +client = TestClient(app) + + +def test_multi_query_values(): + url = "/items/?q=foo&q=bar" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["foo", "bar"]} + + +def test_query_no_values(): + url = "/items/" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": []} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {}, + "default": [], + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py new file mode 100644 index 0000000000..4e90db358b --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py @@ -0,0 +1,106 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial013_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_multi_query_values(client: TestClient): + url = "/items/?q=foo&q=bar" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": ["foo", "bar"]} + + +@needs_py39 +def test_query_no_values(client: TestClient): + url = "/items/" + response = client.get(url) + assert response.status_code == 200, response.text + assert response.json() == {"q": []} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {}, + "default": [], + }, + "name": "q", + "in": "query", + } + ], + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index 57b8b9d946..7686c07b39 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -5,71 +5,6 @@ from docs_src.query_params_str_validations.tutorial014 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_hidden_query(): response = client.get("/items?hidden_query=somevalue") assert response.status_code == 200, response.text @@ -80,3 +15,67 @@ def test_no_hidden_query(): response = client.get("/items") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "Not found"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py new file mode 100644 index 0000000000..e739044a83 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py @@ -0,0 +1,81 @@ +from fastapi.testclient import TestClient + +from docs_src.query_params_str_validations.tutorial014_an import app + +client = TestClient(app) + + +def test_hidden_query(): + response = client.get("/items?hidden_query=somevalue") + assert response.status_code == 200, response.text + assert response.json() == {"hidden_query": "somevalue"} + + +def test_no_hidden_query(): + response = client.get("/items") + assert response.status_code == 200, response.text + assert response.json() == {"hidden_query": "Not found"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py new file mode 100644 index 0000000000..73f0ba78b8 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py @@ -0,0 +1,91 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial014_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_hidden_query(client: TestClient): + response = client.get("/items?hidden_query=somevalue") + assert response.status_code == 200, response.text + assert response.json() == {"hidden_query": "somevalue"} + + +@needs_py310 +def test_no_hidden_query(client: TestClient): + response = client.get("/items") + assert response.status_code == 200, response.text + assert response.json() == {"hidden_query": "Not found"} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py new file mode 100644 index 0000000000..e2c1499921 --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py @@ -0,0 +1,91 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.query_params_str_validations.tutorial014_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_hidden_query(client: TestClient): + response = client.get("/items?hidden_query=somevalue") + assert response.status_code == 200, response.text + assert response.json() == {"hidden_query": "somevalue"} + + +@needs_py310 +def test_no_hidden_query(client: TestClient): + response = client.get("/items") + assert response.status_code == 200, response.text + assert response.json() == {"hidden_query": "Not found"} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py index fe54fc080b..07f30b7395 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py @@ -3,64 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -70,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_hidden_query(client: TestClient): response = client.get("/items?hidden_query=somevalue") @@ -89,3 +24,68 @@ def test_no_hidden_query(client: TestClient): response = client.get("/items") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "Not found"} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index 166014c71f..3269801efc 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -4,128 +4,6 @@ from docs_src.request_files.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - } - }, - "/uploadfile/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - file_required = { "detail": [ @@ -182,3 +60,125 @@ def test_post_upload_file(tmp_path): response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + } + }, + "/uploadfile/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + }, + "required": True, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index a254bf3e8f..4b6edfa066 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -4,124 +4,6 @@ from docs_src.request_files.tutorial001_02 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post_form_no_body(): response = client.post("/files/") @@ -155,3 +37,121 @@ def test_post_upload_file(tmp_path): response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py new file mode 100644 index 0000000000..0c34620e38 --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py @@ -0,0 +1,157 @@ +from fastapi.testclient import TestClient + +from docs_src.request_files.tutorial001_02_an import app + +client = TestClient(app) + + +def test_post_form_no_body(): + response = client.post("/files/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No file sent"} + + +def test_post_uploadfile_no_body(): + response = client.post("/uploadfile/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No upload file sent"} + + +def test_post_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py new file mode 100644 index 0000000000..04442c76f4 --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py @@ -0,0 +1,169 @@ +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.request_files.tutorial001_02_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_post_form_no_body(client: TestClient): + response = client.post("/files/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No file sent"} + + +@needs_py310 +def test_post_uploadfile_no_body(client: TestClient): + response = client.post("/uploadfile/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No upload file sent"} + + +@needs_py310 +def test_post_file(tmp_path: Path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +@needs_py310 +def test_post_upload_file(tmp_path: Path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py new file mode 100644 index 0000000000..f5249ef5bf --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py @@ -0,0 +1,169 @@ +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.request_files.tutorial001_02_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_post_form_no_body(client: TestClient): + response = client.post("/files/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No file sent"} + + +@needs_py39 +def test_post_uploadfile_no_body(client: TestClient): + response = client.post("/uploadfile/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No upload file sent"} + + +@needs_py39 +def test_post_file(tmp_path: Path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +@needs_py39 +def test_post_upload_file(tmp_path: Path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py index 15b6a8d538..f690d107ba 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py @@ -5,118 +5,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -126,13 +14,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_post_form_no_body(client: TestClient): response = client.post("/files/") @@ -167,3 +48,122 @@ def test_post_upload_file(tmp_path: Path, client: TestClient): response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index c34165f18e..4af659a111 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -4,138 +4,6 @@ from docs_src.request_files.tutorial001_03 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as bytes", - "format": "binary", - } - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as UploadFile", - "format": "binary", - } - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post_file(tmp_path): path = tmp_path / "test.txt" @@ -157,3 +25,135 @@ def test_post_upload_file(tmp_path): response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as bytes", + "format": "binary", + } + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as UploadFile", + "format": "binary", + } + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py new file mode 100644 index 0000000000..91dbc60b90 --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py @@ -0,0 +1,159 @@ +from fastapi.testclient import TestClient + +from docs_src.request_files.tutorial001_03_an import app + +client = TestClient(app) + + +def test_post_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as bytes", + "format": "binary", + } + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as UploadFile", + "format": "binary", + } + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py new file mode 100644 index 0000000000..7c4ad326c4 --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py @@ -0,0 +1,167 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.request_files.tutorial001_03_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_post_file(tmp_path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +@needs_py39 +def test_post_upload_file(tmp_path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as bytes", + "format": "binary", + } + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as UploadFile", + "format": "binary", + } + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_an.py new file mode 100644 index 0000000000..80c288ed6e --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_an.py @@ -0,0 +1,184 @@ +from fastapi.testclient import TestClient + +from docs_src.request_files.tutorial001_an import app + +client = TestClient(app) + + +file_required = { + "detail": [ + { + "loc": ["body", "file"], + "msg": "field required", + "type": "value_error.missing", + } + ] +} + + +def test_post_form_no_body(): + response = client.post("/files/") + assert response.status_code == 422, response.text + assert response.json() == file_required + + +def test_post_body_json(): + response = client.post("/files/", json={"file": "Foo"}) + assert response.status_code == 422, response.text + assert response.json() == file_required + + +def test_post_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +def test_post_large_file(tmp_path): + default_pydantic_max_size = 2**16 + path = tmp_path / "test.txt" + path.write_bytes(b"x" * (default_pydantic_max_size + 1)) + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": default_pydantic_max_size + 1} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + } + }, + "/uploadfile/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + }, + "required": True, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..4dc1f752ce --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py @@ -0,0 +1,194 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.request_files.tutorial001_an_py39 import app + + client = TestClient(app) + return client + + +file_required = { + "detail": [ + { + "loc": ["body", "file"], + "msg": "field required", + "type": "value_error.missing", + } + ] +} + + +@needs_py39 +def test_post_form_no_body(client: TestClient): + response = client.post("/files/") + assert response.status_code == 422, response.text + assert response.json() == file_required + + +@needs_py39 +def test_post_body_json(client: TestClient): + response = client.post("/files/", json={"file": "Foo"}) + assert response.status_code == 422, response.text + assert response.json() == file_required + + +@needs_py39 +def test_post_file(tmp_path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +@needs_py39 +def test_post_large_file(tmp_path, client: TestClient): + default_pydantic_max_size = 2**16 + path = tmp_path / "test.txt" + path.write_bytes(b"x" * (default_pydantic_max_size + 1)) + + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": default_pydantic_max_size + 1} + + +@needs_py39 +def test_post_upload_file(tmp_path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + } + }, + "/uploadfile/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + }, + "required": True, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index 73d1179a1c..6868be3284 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -4,148 +4,6 @@ from docs_src.request_files.tutorial002 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Files", - "operationId": "create_files_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_files_files__post" - } - } - }, - "required": True, - }, - } - }, - "/uploadfiles/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Upload Files", - "operationId": "create_upload_files_uploadfiles__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post" - } - } - }, - "required": True, - }, - } - }, - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Main", - "operationId": "main__get", - } - }, - }, - "components": { - "schemas": { - "Body_create_upload_files_uploadfiles__post": { - "title": "Body_create_upload_files_uploadfiles__post", - "required": ["files"], - "type": "object", - "properties": { - "files": { - "title": "Files", - "type": "array", - "items": {"type": "string", "format": "binary"}, - } - }, - }, - "Body_create_files_files__post": { - "title": "Body_create_files_files__post", - "required": ["files"], - "type": "object", - "properties": { - "files": { - "title": "Files", - "type": "array", - "items": {"type": "string", "format": "binary"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - file_required = { "detail": [ @@ -213,3 +71,145 @@ def test_get_root(): response = client.get("/") assert response.status_code == 200, response.text assert b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/files/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_sizes": [14, 15]} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/uploadfiles/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"filenames": ["test.txt", "test2.txt"]} + + +def test_get_root(): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/files/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_sizes": [14, 15]} + + +@needs_py39 +def test_post_upload_file(tmp_path, app: FastAPI): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/uploadfiles/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"filenames": ["test.txt", "test2.txt"]} + + +@needs_py39 +def test_get_root(app: FastAPI): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/files/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_sizes": [14, 15]} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/uploadfiles/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"filenames": ["test.txt", "test2.txt"]} + + +def test_get_root(): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/files/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_sizes": [14, 15]} + + +@needs_py39 +def test_post_upload_file(tmp_path, app: FastAPI): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/uploadfiles/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"filenames": ["test.txt", "test2.txt"]} + + +@needs_py39 +def test_get_root(app: FastAPI): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 422, response.text + assert response.json() == token_required + + +def test_post_files_and_token(tmp_path): + patha = tmp_path / "test.txt" + pathb = tmp_path / "testb.txt" + patha.write_text("") + pathb.write_text("") + + client = TestClient(app) + with patha.open("rb") as filea, pathb.open("rb") as fileb: + response = client.post( + "/files/", + data={"token": "foo"}, + files={"file": filea, "fileb": ("testb.txt", fileb, "text/plain")}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "file_size": 14, + "token": "foo", + "fileb_content_type": "text/plain", + } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file", "fileb", "token"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"}, + "fileb": { + "title": "Fileb", + "type": "string", + "format": "binary", + }, + "token": {"title": "Token", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..9b5e8681c6 --- /dev/null +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py @@ -0,0 +1,215 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="app") +def get_app(): + from docs_src.request_forms_and_files.tutorial001_an_py39 import app + + return app + + +@pytest.fixture(name="client") +def get_client(app: FastAPI): + client = TestClient(app) + return client + + +file_required = { + "detail": [ + { + "loc": ["body", "file"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "fileb"], + "msg": "field required", + "type": "value_error.missing", + }, + ] +} + +token_required = { + "detail": [ + { + "loc": ["body", "fileb"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "token"], + "msg": "field required", + "type": "value_error.missing", + }, + ] +} + +# {'detail': [, {'loc': ['body', 'token'], 'msg': 'field required', 'type': 'value_error.missing'}]} + +file_and_token_required = { + "detail": [ + { + "loc": ["body", "file"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "fileb"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "token"], + "msg": "field required", + "type": "value_error.missing", + }, + ] +} + + +@needs_py39 +def test_post_form_no_body(client: TestClient): + response = client.post("/files/") + assert response.status_code == 422, response.text + assert response.json() == file_and_token_required + + +@needs_py39 +def test_post_form_no_file(client: TestClient): + response = client.post("/files/", data={"token": "foo"}) + assert response.status_code == 422, response.text + assert response.json() == file_required + + +@needs_py39 +def test_post_body_json(client: TestClient): + response = client.post("/files/", json={"file": "Foo", "token": "Bar"}) + assert response.status_code == 422, response.text + assert response.json() == file_and_token_required + + +@needs_py39 +def test_post_file_no_token(tmp_path, app: FastAPI): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 422, response.text + assert response.json() == token_required + + +@needs_py39 +def test_post_files_and_token(tmp_path, app: FastAPI): + patha = tmp_path / "test.txt" + pathb = tmp_path / "testb.txt" + patha.write_text("") + pathb.write_text("") + + client = TestClient(app) + with patha.open("rb") as filea, pathb.open("rb") as fileb: + response = client.post( + "/files/", + data={"token": "foo"}, + files={"file": filea, "fileb": ("testb.txt", fileb, "text/plain")}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "file_size": 14, + "token": "foo", + "fileb_content_type": "text/plain", + } + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file", "fileb", "token"], + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"}, + "fileb": { + "title": "Fileb", + "type": "string", + "format": "binary", + }, + "token": {"title": "Token", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py index e1bde5d133..4a95cb2b56 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003.py @@ -4,103 +4,6 @@ from docs_src.response_model.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/user/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserOut"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_user__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserIn"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "UserOut": { - "title": "UserOut", - "required": ["username", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string", "format": "email"}, - "full_name": {"title": "Full Name", "type": "string"}, - }, - }, - "UserIn": { - "title": "UserIn", - "required": ["username", "password", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "email": {"title": "Email", "type": "string", "format": "email"}, - "full_name": {"title": "Full Name", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_post_user(): response = client.post( @@ -118,3 +21,108 @@ def test_post_user(): "email": "foo@example.com", "full_name": "Grave Dohl", } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/user/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserOut"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_user__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserIn"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "UserOut": { + "title": "UserOut", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "UserIn": { + "title": "UserIn", + "required": ["username", "password", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py new file mode 100644 index 0000000000..a055bc6885 --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py @@ -0,0 +1,128 @@ +from fastapi.testclient import TestClient + +from docs_src.response_model.tutorial003_01 import app + +client = TestClient(app) + + +def test_post_user(): + response = client.post( + "/user/", + json={ + "username": "foo", + "password": "fighter", + "email": "foo@example.com", + "full_name": "Grave Dohl", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "foo", + "email": "foo@example.com", + "full_name": "Grave Dohl", + } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/user/": { + "post": { + "summary": "Create User", + "operationId": "create_user_user__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserIn"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/BaseUser"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "BaseUser": { + "title": "BaseUser", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "UserIn": { + "title": "UserIn", + "required": ["username", "email", "password"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py new file mode 100644 index 0000000000..8cb4ac208e --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py @@ -0,0 +1,137 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.response_model.tutorial003_01_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_post_user(client: TestClient): + response = client.post( + "/user/", + json={ + "username": "foo", + "password": "fighter", + "email": "foo@example.com", + "full_name": "Grave Dohl", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "foo", + "email": "foo@example.com", + "full_name": "Grave Dohl", + } + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/user/": { + "post": { + "summary": "Create User", + "operationId": "create_user_user__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserIn"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/BaseUser"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "BaseUser": { + "title": "BaseUser", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "UserIn": { + "title": "UserIn", + "required": ["username", "email", "password"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_02.py b/tests/test_tutorial/test_response_model/test_tutorial003_02.py new file mode 100644 index 0000000000..6ccb054b83 --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_02.py @@ -0,0 +1,93 @@ +from fastapi.testclient import TestClient + +from docs_src.response_model.tutorial003_02 import app + +client = TestClient(app) + + +def test_get_portal(): + response = client.get("/portal") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Here's your interdimensional portal."} + + +def test_get_redirect(): + response = client.get("/portal", params={"teleport": True}, follow_redirects=False) + assert response.status_code == 307, response.text + assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/portal": { + "get": { + "summary": "Get Portal", + "operationId": "get_portal_portal_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Teleport", + "type": "boolean", + "default": False, + }, + "name": "teleport", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_03.py b/tests/test_tutorial/test_response_model/test_tutorial003_03.py new file mode 100644 index 0000000000..ba4c0f2750 --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_03.py @@ -0,0 +1,34 @@ +from fastapi.testclient import TestClient + +from docs_src.response_model.tutorial003_03 import app + +client = TestClient(app) + + +def test_get_portal(): + response = client.get("/teleport", follow_redirects=False) + assert response.status_code == 307, response.text + assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/teleport": { + "get": { + "summary": "Get Teleport", + "operationId": "get_teleport_teleport_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_04.py b/tests/test_tutorial/test_response_model/test_tutorial003_04.py new file mode 100644 index 0000000000..4aa80145a5 --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_04.py @@ -0,0 +1,9 @@ +import pytest +from fastapi.exceptions import FastAPIError + + +def test_invalid_response_model(): + with pytest.raises(FastAPIError): + from docs_src.response_model.tutorial003_04 import app + + assert app # pragma: no cover diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_04_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_04_py310.py new file mode 100644 index 0000000000..b876facc8b --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_04_py310.py @@ -0,0 +1,12 @@ +import pytest +from fastapi.exceptions import FastAPIError + +from ...utils import needs_py310 + + +@needs_py310 +def test_invalid_response_model(): + with pytest.raises(FastAPIError): + from docs_src.response_model.tutorial003_04_py310 import app + + assert app # pragma: no cover diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05.py b/tests/test_tutorial/test_response_model/test_tutorial003_05.py new file mode 100644 index 0000000000..d7c232e751 --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py @@ -0,0 +1,93 @@ +from fastapi.testclient import TestClient + +from docs_src.response_model.tutorial003_05 import app + +client = TestClient(app) + + +def test_get_portal(): + response = client.get("/portal") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Here's your interdimensional portal."} + + +def test_get_redirect(): + response = client.get("/portal", params={"teleport": True}, follow_redirects=False) + assert response.status_code == 307, response.text + assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/portal": { + "get": { + "summary": "Get Portal", + "operationId": "get_portal_portal_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Teleport", + "type": "boolean", + "default": False, + }, + "name": "teleport", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py new file mode 100644 index 0000000000..a89f1dad8b --- /dev/null +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py @@ -0,0 +1,103 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.response_model.tutorial003_05_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_get_portal(client: TestClient): + response = client.get("/portal") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Here's your interdimensional portal."} + + +@needs_py310 +def test_get_redirect(client: TestClient): + response = client.get("/portal", params={"teleport": True}, follow_redirects=False) + assert response.status_code == 307, response.text + assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/portal": { + "get": { + "summary": "Get Portal", + "operationId": "get_portal_portal_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Teleport", + "type": "boolean", + "default": False, + }, + "name": "teleport", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_py310.py index 9827dab8a3..1f4ec9057f 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_py310.py @@ -3,97 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/user/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserOut"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_user__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserIn"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "UserOut": { - "title": "UserOut", - "required": ["username", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string", "format": "email"}, - "full_name": {"title": "Full Name", "type": "string"}, - }, - }, - "UserIn": { - "title": "UserIn", - "required": ["username", "password", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "email": {"title": "Email", "type": "string", "format": "email"}, - "full_name": {"title": "Full Name", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -103,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_post_user(client: TestClient): response = client.post( @@ -127,3 +29,109 @@ def test_post_user(client: TestClient): "email": "foo@example.com", "full_name": "Grave Dohl", } + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/user/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserOut"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_user__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserIn"} + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "UserOut": { + "title": "UserOut", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "UserIn": { + "title": "UserIn", + "required": ["username", "password", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": {"title": "Full Name", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py index 8c98c6de30..8cb7dc9cf4 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004.py @@ -5,99 +5,6 @@ from docs_src.response_model.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - @pytest.mark.parametrize( "url,data", @@ -123,3 +30,96 @@ def test_get(url, data): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == data + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py index 7fc86fafab..2ba8143ba6 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py @@ -3,93 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -99,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 @pytest.mark.parametrize( "url,data", @@ -131,3 +37,97 @@ def test_get(url, data, client: TestClient): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == data + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py b/tests/test_tutorial/test_response_model/test_tutorial004_py39.py index 405fe79f50..97df0a238f 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004_py39.py @@ -3,93 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -99,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 @pytest.mark.parametrize( "url,data", @@ -131,3 +37,97 @@ def test_get(url, data, client: TestClient): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == data + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py index 476b172d3d..76662f7937 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005.py @@ -4,127 +4,6 @@ from docs_src.response_model.tutorial005 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}/name": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Name", - "operationId": "read_item_name_items__item_id__name_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/items/{item_id}/public": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Public Data", - "operationId": "read_item_public_data_items__item_id__public_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_read_item_name(): response = client.get("/items/bar/name") @@ -140,3 +19,124 @@ def test_read_item_public_data(): "description": "The Bar fighters", "price": 62, } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}/name": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Name", + "operationId": "read_item_name_items__item_id__name_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/items/{item_id}/public": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Public Data", + "operationId": "read_item_public_data_items__item_id__public_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py index 389a302e08..1b1cf41755 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py @@ -3,121 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}/name": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Name", - "operationId": "read_item_name_items__item_id__name_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/items/{item_id}/public": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Public Data", - "operationId": "read_item_public_data_items__item_id__public_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -127,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_read_item_name(client: TestClient): response = client.get("/items/bar/name") @@ -150,3 +28,125 @@ def test_read_item_public_data(client: TestClient): "description": "The Bar fighters", "price": 62, } + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}/name": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Name", + "operationId": "read_item_name_items__item_id__name_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/items/{item_id}/public": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Public Data", + "operationId": "read_item_public_data_items__item_id__public_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py index 38eb31e54f..3a759fde41 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006.py @@ -4,127 +4,6 @@ from docs_src.response_model.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}/name": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Name", - "operationId": "read_item_name_items__item_id__name_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/items/{item_id}/public": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Public Data", - "operationId": "read_item_public_data_items__item_id__public_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_read_item_name(): response = client.get("/items/bar/name") @@ -140,3 +19,124 @@ def test_read_item_public_data(): "description": "The Bar fighters", "price": 62, } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}/name": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Name", + "operationId": "read_item_name_items__item_id__name_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/items/{item_id}/public": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Public Data", + "operationId": "read_item_public_data_items__item_id__public_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py index f870f39261..07e84cc82a 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py @@ -3,121 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}/name": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Name", - "operationId": "read_item_name_items__item_id__name_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/items/{item_id}/public": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item Public Data", - "operationId": "read_item_public_data_items__item_id__public_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": {"title": "Description", "type": "string"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -127,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_read_item_name(client: TestClient): response = client.get("/items/bar/name") @@ -150,3 +28,125 @@ def test_read_item_public_data(client: TestClient): "description": "The Bar fighters", "price": 62, } + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}/name": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Name", + "operationId": "read_item_name_items__item_id__name_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/items/{item_id}/public": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item Public Data", + "operationId": "read_item_public_data_items__item_id__public_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": {"title": "Description", "type": "string"}, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py index badf66b3d1..784517d69e 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py @@ -4,121 +4,6 @@ from docs_src.schema_extra_example.tutorial004 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "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", - }, - }, - }, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - # Test required and embedded body parameters with no bodies sent def test_post_body_example(): @@ -132,3 +17,118 @@ def test_post_body_example(): }, ) assert response.status_code == 200 + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "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", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py new file mode 100644 index 0000000000..222a4edfe8 --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py @@ -0,0 +1,134 @@ +from fastapi.testclient import TestClient + +from docs_src.schema_extra_example.tutorial004_an import app + +client = TestClient(app) + + +# Test required and embedded body parameters with no bodies sent +def test_post_body_example(): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + ) + assert response.status_code == 200 + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "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", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py new file mode 100644 index 0000000000..1eacd640af --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py @@ -0,0 +1,143 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.schema_extra_example.tutorial004_an_py310 import app + + client = TestClient(app) + return client + + +# Test required and embedded body parameters with no bodies sent +@needs_py310 +def test_post_body_example(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + ) + assert response.status_code == 200 + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "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", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py new file mode 100644 index 0000000000..632f2cbe0b --- /dev/null +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py @@ -0,0 +1,143 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.schema_extra_example.tutorial004_an_py39 import app + + client = TestClient(app) + return client + + +# Test required and embedded body parameters with no bodies sent +@needs_py39 +def test_post_body_example(client: TestClient): + response = client.put( + "/items/5", + json={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + ) + assert response.status_code == 200 + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "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", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py index d326a5a092..c99cb75c89 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py @@ -3,115 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "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", - }, - }, - }, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - @pytest.fixture(name="client") def get_client(): @@ -121,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - # Test required and embedded body parameters with no bodies sent @needs_py310 def test_post_body_example(client: TestClient): @@ -141,3 +25,119 @@ def test_post_body_example(client: TestClient): }, ) assert response.status_code == 200 + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "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", + }, + }, + }, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "tax": {"title": "Tax", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py index 8a033c4f26..a7f55b78b2 100644 --- a/tests/test_tutorial/test_security/test_tutorial001.py +++ b/tests/test_tutorial/test_security/test_tutorial001.py @@ -4,40 +4,6 @@ from docs_src.security.tutorial001 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2PasswordBearer": []}], - } - } - }, - "components": { - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, - } - } - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_no_token(): response = client.get("/items") @@ -57,3 +23,35 @@ def test_incorrect_token(): assert response.status_code == 401, response.text assert response.json() == {"detail": "Not authenticated"} assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + } + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial001_an.py b/tests/test_tutorial/test_security/test_tutorial001_an.py new file mode 100644 index 0000000000..fc48703aa0 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial001_an.py @@ -0,0 +1,57 @@ +from fastapi.testclient import TestClient + +from docs_src.security.tutorial001_an import app + +client = TestClient(app) + + +def test_no_token(): + response = client.get("/items") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token(): + response = client.get("/items", headers={"Authorization": "Bearer testtoken"}) + assert response.status_code == 200, response.text + assert response.json() == {"token": "testtoken"} + + +def test_incorrect_token(): + response = client.get("/items", headers={"Authorization": "Notexistent testtoken"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + } + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial001_an_py39.py b/tests/test_tutorial/test_security/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..345e0be0f3 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial001_an_py39.py @@ -0,0 +1,68 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.security.tutorial001_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_no_token(client: TestClient): + response = client.get("/items") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_token(client: TestClient): + response = client.get("/items", headers={"Authorization": "Bearer testtoken"}) + assert response.status_code == 200, response.text + assert response.json() == {"token": "testtoken"} + + +@needs_py39 +def test_incorrect_token(client: TestClient): + response = client.get("/items", headers={"Authorization": "Notexistent testtoken"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + } + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py index 5951078343..c10f928eb4 100644 --- a/tests/test_tutorial/test_security/test_tutorial003.py +++ b/tests/test_tutorial/test_security/test_tutorial003.py @@ -4,116 +4,6 @@ from docs_src.security.tutorial003 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/token": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_token_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_token_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_token_post": { - "title": "Body_login_token_post", - "required": ["username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, - } - }, - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_login(): response = client.post("/token", data={"username": "johndoe", "password": "secret"}) @@ -174,3 +64,113 @@ def test_inactive_user(): response = client.get("/users/me", headers={"Authorization": "Bearer alice"}) assert response.status_code == 400, response.text assert response.json() == {"detail": "Inactive user"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_token_post": { + "title": "Body_login_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial003_an.py b/tests/test_tutorial/test_security/test_tutorial003_an.py new file mode 100644 index 0000000000..41872fda03 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial003_an.py @@ -0,0 +1,176 @@ +from fastapi.testclient import TestClient + +from docs_src.security.tutorial003_an import app + +client = TestClient(app) + + +def test_login(): + response = client.post("/token", data={"username": "johndoe", "password": "secret"}) + assert response.status_code == 200, response.text + assert response.json() == {"access_token": "johndoe", "token_type": "bearer"} + + +def test_login_incorrect_password(): + response = client.post( + "/token", data={"username": "johndoe", "password": "incorrect"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +def test_login_incorrect_username(): + response = client.post("/token", data={"username": "foo", "password": "secret"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +def test_no_token(): + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token(): + response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"}) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "hashed_password": "fakehashedsecret", + "disabled": False, + } + + +def test_incorrect_token(): + response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Invalid authentication credentials"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_incorrect_token_type(): + response = client.get( + "/users/me", headers={"Authorization": "Notexistent testtoken"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_inactive_user(): + response = client.get("/users/me", headers={"Authorization": "Bearer alice"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Inactive user"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_token_post": { + "title": "Body_login_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial003_an_py310.py b/tests/test_tutorial/test_security/test_tutorial003_an_py310.py new file mode 100644 index 0000000000..02bd748c8c --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial003_an_py310.py @@ -0,0 +1,192 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.security.tutorial003_an_py310 import app + + client = TestClient(app) + return client + + +@needs_py310 +def test_login(client: TestClient): + response = client.post("/token", data={"username": "johndoe", "password": "secret"}) + assert response.status_code == 200, response.text + assert response.json() == {"access_token": "johndoe", "token_type": "bearer"} + + +@needs_py310 +def test_login_incorrect_password(client: TestClient): + response = client.post( + "/token", data={"username": "johndoe", "password": "incorrect"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py310 +def test_login_incorrect_username(client: TestClient): + response = client.post("/token", data={"username": "foo", "password": "secret"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py310 +def test_no_token(client: TestClient): + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py310 +def test_token(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"}) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "hashed_password": "fakehashedsecret", + "disabled": False, + } + + +@needs_py310 +def test_incorrect_token(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Invalid authentication credentials"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py310 +def test_incorrect_token_type(client: TestClient): + response = client.get( + "/users/me", headers={"Authorization": "Notexistent testtoken"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py310 +def test_inactive_user(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer alice"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Inactive user"} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_token_post": { + "title": "Body_login_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial003_an_py39.py b/tests/test_tutorial/test_security/test_tutorial003_an_py39.py new file mode 100644 index 0000000000..7e74afafb1 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial003_an_py39.py @@ -0,0 +1,192 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.security.tutorial003_an_py39 import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_login(client: TestClient): + response = client.post("/token", data={"username": "johndoe", "password": "secret"}) + assert response.status_code == 200, response.text + assert response.json() == {"access_token": "johndoe", "token_type": "bearer"} + + +@needs_py39 +def test_login_incorrect_password(client: TestClient): + response = client.post( + "/token", data={"username": "johndoe", "password": "incorrect"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py39 +def test_login_incorrect_username(client: TestClient): + response = client.post("/token", data={"username": "foo", "password": "secret"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py39 +def test_no_token(client: TestClient): + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_token(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"}) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "hashed_password": "fakehashedsecret", + "disabled": False, + } + + +@needs_py39 +def test_incorrect_token(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Invalid authentication credentials"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_incorrect_token_type(client: TestClient): + response = client.get( + "/users/me", headers={"Authorization": "Notexistent testtoken"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_inactive_user(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer alice"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Inactive user"} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_token_post": { + "title": "Body_login_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial003_py310.py b/tests/test_tutorial/test_security/test_tutorial003_py310.py index 26f5c097ff..a463751f5e 100644 --- a/tests/test_tutorial/test_security/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_security/test_tutorial003_py310.py @@ -3,110 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/token": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_token_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_token_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_token_post": { - "title": "Body_login_token_post", - "required": ["username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, - } - }, - }, -} - @pytest.fixture(name="client") def get_client(): @@ -116,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_login(client: TestClient): response = client.post("/token", data={"username": "johndoe", "password": "secret"}) @@ -190,3 +79,114 @@ def test_inactive_user(client: TestClient): response = client.get("/users/me", headers={"Authorization": "Bearer alice"}) assert response.status_code == 400, response.text assert response.json() == {"detail": "Inactive user"} + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login", + "operationId": "login_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "Body_login_token_post": { + "title": "Body_login_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py index e8697339ff..ccb5b3c9f8 100644 --- a/tests/test_tutorial/test_security/test_tutorial005.py +++ b/tests/test_tutorial/test_security/test_tutorial005.py @@ -10,178 +10,6 @@ from docs_src.security.tutorial005 import ( client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/token": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Token"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login For Access Token", - "operationId": "login_for_access_token_token_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_for_access_token_token_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me__get", - "security": [{"OAuth2PasswordBearer": ["me"]}], - } - }, - "/users/me/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Own Items", - "operationId": "read_own_items_users_me_items__get", - "security": [{"OAuth2PasswordBearer": ["items", "me"]}], - } - }, - "/status/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read System Status", - "operationId": "read_system_status_status__get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string"}, - "full_name": {"title": "Full Name", "type": "string"}, - "disabled": {"title": "Disabled", "type": "boolean"}, - }, - }, - "Token": { - "title": "Token", - "required": ["access_token", "token_type"], - "type": "object", - "properties": { - "access_token": {"title": "Access Token", "type": "string"}, - "token_type": {"title": "Token Type", "type": "string"}, - }, - }, - "Body_login_for_access_token_token_post": { - "title": "Body_login_for_access_token_token_post", - "required": ["username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "me": "Read information about the current user.", - "items": "Read items.", - }, - "tokenUrl": "token", - } - }, - } - }, - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def get_access_token(username="johndoe", password="secret", scope=None): data = {"username": username, "password": password} @@ -345,3 +173,175 @@ def test_read_system_status_no_token(): assert response.status_code == 401, response.text assert response.json() == {"detail": "Not authenticated"} assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Token"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login For Access Token", + "operationId": "login_for_access_token_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_for_access_token_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me__get", + "security": [{"OAuth2PasswordBearer": ["me"]}], + } + }, + "/users/me/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Own Items", + "operationId": "read_own_items_users_me_items__get", + "security": [{"OAuth2PasswordBearer": ["items", "me"]}], + } + }, + "/status/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read System Status", + "operationId": "read_system_status_status__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + "disabled": {"title": "Disabled", "type": "boolean"}, + }, + }, + "Token": { + "title": "Token", + "required": ["access_token", "token_type"], + "type": "object", + "properties": { + "access_token": {"title": "Access Token", "type": "string"}, + "token_type": {"title": "Token Type", "type": "string"}, + }, + }, + "Body_login_for_access_token_token_post": { + "title": "Body_login_for_access_token_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "me": "Read information about the current user.", + "items": "Read items.", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial005_an.py b/tests/test_tutorial/test_security/test_tutorial005_an.py new file mode 100644 index 0000000000..e851f47fec --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial005_an.py @@ -0,0 +1,347 @@ +from fastapi.testclient import TestClient + +from docs_src.security.tutorial005_an import ( + app, + create_access_token, + fake_users_db, + get_password_hash, + verify_password, +) + +client = TestClient(app) + + +def get_access_token(username="johndoe", password="secret", scope=None): + data = {"username": username, "password": password} + if scope: + data["scope"] = scope + response = client.post("/token", data=data) + content = response.json() + access_token = content.get("access_token") + return access_token + + +def test_login(): + response = client.post("/token", data={"username": "johndoe", "password": "secret"}) + assert response.status_code == 200, response.text + content = response.json() + assert "access_token" in content + assert content["token_type"] == "bearer" + + +def test_login_incorrect_password(): + response = client.post( + "/token", data={"username": "johndoe", "password": "incorrect"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +def test_login_incorrect_username(): + response = client.post("/token", data={"username": "foo", "password": "secret"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +def test_no_token(): + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_token(): + access_token = get_access_token(scope="me") + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "disabled": False, + } + + +def test_incorrect_token(): + response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +def test_incorrect_token_type(): + response = client.get( + "/users/me", headers={"Authorization": "Notexistent testtoken"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_verify_password(): + assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"]) + + +def test_get_password_hash(): + assert get_password_hash("secretalice") + + +def test_create_access_token(): + access_token = create_access_token(data={"data": "foo"}) + assert access_token + + +def test_token_no_sub(): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +def test_token_no_username(): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +def test_token_no_scope(): + access_token = get_access_token() + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not enough permissions"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +def test_token_inexistent_user(): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +def test_token_inactive_user(): + access_token = get_access_token( + username="alice", password="secretalice", scope="me" + ) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Inactive user"} + + +def test_read_items(): + access_token = get_access_token(scope="me items") + response = client.get( + "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] + + +def test_read_system_status(): + access_token = get_access_token() + response = client.get( + "/status/", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"status": "ok"} + + +def test_read_system_status_no_token(): + response = client.get("/status/") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Token"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login For Access Token", + "operationId": "login_for_access_token_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_for_access_token_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me__get", + "security": [{"OAuth2PasswordBearer": ["me"]}], + } + }, + "/users/me/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Own Items", + "operationId": "read_own_items_users_me_items__get", + "security": [{"OAuth2PasswordBearer": ["items", "me"]}], + } + }, + "/status/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read System Status", + "operationId": "read_system_status_status__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + "disabled": {"title": "Disabled", "type": "boolean"}, + }, + }, + "Token": { + "title": "Token", + "required": ["access_token", "token_type"], + "type": "object", + "properties": { + "access_token": {"title": "Access Token", "type": "string"}, + "token_type": {"title": "Token Type", "type": "string"}, + }, + }, + "Body_login_for_access_token_token_post": { + "title": "Body_login_for_access_token_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "me": "Read information about the current user.", + "items": "Read items.", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial005_an_py310.py b/tests/test_tutorial/test_security/test_tutorial005_an_py310.py new file mode 100644 index 0000000000..cca5819802 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial005_an_py310.py @@ -0,0 +1,375 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.security.tutorial005_an_py310 import app + + client = TestClient(app) + return client + + +def get_access_token( + *, username="johndoe", password="secret", scope=None, client: TestClient +): + data = {"username": username, "password": password} + if scope: + data["scope"] = scope + response = client.post("/token", data=data) + content = response.json() + access_token = content.get("access_token") + return access_token + + +@needs_py310 +def test_login(client: TestClient): + response = client.post("/token", data={"username": "johndoe", "password": "secret"}) + assert response.status_code == 200, response.text + content = response.json() + assert "access_token" in content + assert content["token_type"] == "bearer" + + +@needs_py310 +def test_login_incorrect_password(client: TestClient): + response = client.post( + "/token", data={"username": "johndoe", "password": "incorrect"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py310 +def test_login_incorrect_username(client: TestClient): + response = client.post("/token", data={"username": "foo", "password": "secret"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py310 +def test_no_token(client: TestClient): + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py310 +def test_token(client: TestClient): + access_token = get_access_token(scope="me", client=client) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "disabled": False, + } + + +@needs_py310 +def test_incorrect_token(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py310 +def test_incorrect_token_type(client: TestClient): + response = client.get( + "/users/me", headers={"Authorization": "Notexistent testtoken"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py310 +def test_verify_password(): + from docs_src.security.tutorial005_an_py310 import fake_users_db, verify_password + + assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"]) + + +@needs_py310 +def test_get_password_hash(): + from docs_src.security.tutorial005_an_py310 import get_password_hash + + assert get_password_hash("secretalice") + + +@needs_py310 +def test_create_access_token(): + from docs_src.security.tutorial005_an_py310 import create_access_token + + access_token = create_access_token(data={"data": "foo"}) + assert access_token + + +@needs_py310 +def test_token_no_sub(client: TestClient): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py310 +def test_token_no_username(client: TestClient): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py310 +def test_token_no_scope(client: TestClient): + access_token = get_access_token(client=client) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not enough permissions"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py310 +def test_token_inexistent_user(client: TestClient): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py310 +def test_token_inactive_user(client: TestClient): + access_token = get_access_token( + username="alice", password="secretalice", scope="me", client=client + ) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Inactive user"} + + +@needs_py310 +def test_read_items(client: TestClient): + access_token = get_access_token(scope="me items", client=client) + response = client.get( + "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] + + +@needs_py310 +def test_read_system_status(client: TestClient): + access_token = get_access_token(client=client) + response = client.get( + "/status/", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"status": "ok"} + + +@needs_py310 +def test_read_system_status_no_token(client: TestClient): + response = client.get("/status/") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Token"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login For Access Token", + "operationId": "login_for_access_token_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_for_access_token_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me__get", + "security": [{"OAuth2PasswordBearer": ["me"]}], + } + }, + "/users/me/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Own Items", + "operationId": "read_own_items_users_me_items__get", + "security": [{"OAuth2PasswordBearer": ["items", "me"]}], + } + }, + "/status/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read System Status", + "operationId": "read_system_status_status__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + "disabled": {"title": "Disabled", "type": "boolean"}, + }, + }, + "Token": { + "title": "Token", + "required": ["access_token", "token_type"], + "type": "object", + "properties": { + "access_token": {"title": "Access Token", "type": "string"}, + "token_type": {"title": "Token Type", "type": "string"}, + }, + }, + "Body_login_for_access_token_token_post": { + "title": "Body_login_for_access_token_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "me": "Read information about the current user.", + "items": "Read items.", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial005_an_py39.py b/tests/test_tutorial/test_security/test_tutorial005_an_py39.py new file mode 100644 index 0000000000..eae8514575 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial005_an_py39.py @@ -0,0 +1,375 @@ +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.security.tutorial005_an_py39 import app + + client = TestClient(app) + return client + + +def get_access_token( + *, username="johndoe", password="secret", scope=None, client: TestClient +): + data = {"username": username, "password": password} + if scope: + data["scope"] = scope + response = client.post("/token", data=data) + content = response.json() + access_token = content.get("access_token") + return access_token + + +@needs_py39 +def test_login(client: TestClient): + response = client.post("/token", data={"username": "johndoe", "password": "secret"}) + assert response.status_code == 200, response.text + content = response.json() + assert "access_token" in content + assert content["token_type"] == "bearer" + + +@needs_py39 +def test_login_incorrect_password(client: TestClient): + response = client.post( + "/token", data={"username": "johndoe", "password": "incorrect"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py39 +def test_login_incorrect_username(client: TestClient): + response = client.post("/token", data={"username": "foo", "password": "secret"}) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Incorrect username or password"} + + +@needs_py39 +def test_no_token(client: TestClient): + response = client.get("/users/me") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_token(client: TestClient): + access_token = get_access_token(scope="me", client=client) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "disabled": False, + } + + +@needs_py39 +def test_incorrect_token(client: TestClient): + response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py39 +def test_incorrect_token_type(client: TestClient): + response = client.get( + "/users/me", headers={"Authorization": "Notexistent testtoken"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_verify_password(): + from docs_src.security.tutorial005_an_py39 import fake_users_db, verify_password + + assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"]) + + +@needs_py39 +def test_get_password_hash(): + from docs_src.security.tutorial005_an_py39 import get_password_hash + + assert get_password_hash("secretalice") + + +@needs_py39 +def test_create_access_token(): + from docs_src.security.tutorial005_an_py39 import create_access_token + + access_token = create_access_token(data={"data": "foo"}) + assert access_token + + +@needs_py39 +def test_token_no_sub(client: TestClient): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py39 +def test_token_no_username(client: TestClient): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py39 +def test_token_no_scope(client: TestClient): + access_token = get_access_token(client=client) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not enough permissions"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py39 +def test_token_inexistent_user(client: TestClient): + response = client.get( + "/users/me", + headers={ + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw" + }, + ) + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Could not validate credentials"} + assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' + + +@needs_py39 +def test_token_inactive_user(client: TestClient): + access_token = get_access_token( + username="alice", password="secretalice", scope="me", client=client + ) + response = client.get( + "/users/me", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 400, response.text + assert response.json() == {"detail": "Inactive user"} + + +@needs_py39 +def test_read_items(client: TestClient): + access_token = get_access_token(scope="me items", client=client) + response = client.get( + "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] + + +@needs_py39 +def test_read_system_status(client: TestClient): + access_token = get_access_token(client=client) + response = client.get( + "/status/", headers={"Authorization": f"Bearer {access_token}"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"status": "ok"} + + +@needs_py39 +def test_read_system_status_no_token(client: TestClient): + response = client.get("/status/") + assert response.status_code == 401, response.text + assert response.json() == {"detail": "Not authenticated"} + assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Token"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login For Access Token", + "operationId": "login_for_access_token_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_for_access_token_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me__get", + "security": [{"OAuth2PasswordBearer": ["me"]}], + } + }, + "/users/me/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Own Items", + "operationId": "read_own_items_users_me_items__get", + "security": [{"OAuth2PasswordBearer": ["items", "me"]}], + } + }, + "/status/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read System Status", + "operationId": "read_system_status_status__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + "disabled": {"title": "Disabled", "type": "boolean"}, + }, + }, + "Token": { + "title": "Token", + "required": ["access_token", "token_type"], + "type": "object", + "properties": { + "access_token": {"title": "Access Token", "type": "string"}, + "token_type": {"title": "Token Type", "type": "string"}, + }, + }, + "Body_login_for_access_token_token_post": { + "title": "Body_login_for_access_token_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "me": "Read information about the current user.", + "items": "Read items.", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial005_py310.py b/tests/test_tutorial/test_security/test_tutorial005_py310.py index 3144a23657..cdbd8f75ec 100644 --- a/tests/test_tutorial/test_security/test_tutorial005_py310.py +++ b/tests/test_tutorial/test_security/test_tutorial005_py310.py @@ -3,172 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/token": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Token"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login For Access Token", - "operationId": "login_for_access_token_token_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_for_access_token_token_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me__get", - "security": [{"OAuth2PasswordBearer": ["me"]}], - } - }, - "/users/me/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Own Items", - "operationId": "read_own_items_users_me_items__get", - "security": [{"OAuth2PasswordBearer": ["items", "me"]}], - } - }, - "/status/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read System Status", - "operationId": "read_system_status_status__get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string"}, - "full_name": {"title": "Full Name", "type": "string"}, - "disabled": {"title": "Disabled", "type": "boolean"}, - }, - }, - "Token": { - "title": "Token", - "required": ["access_token", "token_type"], - "type": "object", - "properties": { - "access_token": {"title": "Access Token", "type": "string"}, - "token_type": {"title": "Token Type", "type": "string"}, - }, - }, - "Body_login_for_access_token_token_post": { - "title": "Body_login_for_access_token_token_post", - "required": ["username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "me": "Read information about the current user.", - "items": "Read items.", - }, - "tokenUrl": "token", - } - }, - } - }, - }, -} - @pytest.fixture(name="client") def get_client(): @@ -178,13 +12,6 @@ def get_client(): return client -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def get_access_token( *, username="johndoe", password="secret", scope=None, client: TestClient ): @@ -373,3 +200,176 @@ def test_read_system_status_no_token(client: TestClient): assert response.status_code == 401, response.text assert response.json() == {"detail": "Not authenticated"} assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py310 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Token"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login For Access Token", + "operationId": "login_for_access_token_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_for_access_token_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me__get", + "security": [{"OAuth2PasswordBearer": ["me"]}], + } + }, + "/users/me/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Own Items", + "operationId": "read_own_items_users_me_items__get", + "security": [{"OAuth2PasswordBearer": ["items", "me"]}], + } + }, + "/status/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read System Status", + "operationId": "read_system_status_status__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + "disabled": {"title": "Disabled", "type": "boolean"}, + }, + }, + "Token": { + "title": "Token", + "required": ["access_token", "token_type"], + "type": "object", + "properties": { + "access_token": {"title": "Access Token", "type": "string"}, + "token_type": {"title": "Token Type", "type": "string"}, + }, + }, + "Body_login_for_access_token_token_post": { + "title": "Body_login_for_access_token_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "me": "Read information about the current user.", + "items": "Read items.", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial005_py39.py b/tests/test_tutorial/test_security/test_tutorial005_py39.py index 290136e179..45b2a2341d 100644 --- a/tests/test_tutorial/test_security/test_tutorial005_py39.py +++ b/tests/test_tutorial/test_security/test_tutorial005_py39.py @@ -3,172 +3,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/token": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Token"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login For Access Token", - "operationId": "login_for_access_token_token_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_for_access_token_token_post" - } - } - }, - "required": True, - }, - } - }, - "/users/me/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me__get", - "security": [{"OAuth2PasswordBearer": ["me"]}], - } - }, - "/users/me/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Own Items", - "operationId": "read_own_items_users_me_items__get", - "security": [{"OAuth2PasswordBearer": ["items", "me"]}], - } - }, - "/status/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read System Status", - "operationId": "read_system_status_status__get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string"}, - "full_name": {"title": "Full Name", "type": "string"}, - "disabled": {"title": "Disabled", "type": "boolean"}, - }, - }, - "Token": { - "title": "Token", - "required": ["access_token", "token_type"], - "type": "object", - "properties": { - "access_token": {"title": "Access Token", "type": "string"}, - "token_type": {"title": "Token Type", "type": "string"}, - }, - }, - "Body_login_for_access_token_token_post": { - "title": "Body_login_for_access_token_token_post", - "required": ["username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "password", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": {"title": "Client Id", "type": "string"}, - "client_secret": {"title": "Client Secret", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "me": "Read information about the current user.", - "items": "Read items.", - }, - "tokenUrl": "token", - } - }, - } - }, - }, -} - @pytest.fixture(name="client") def get_client(): @@ -178,13 +12,6 @@ def get_client(): return client -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def get_access_token( *, username="johndoe", password="secret", scope=None, client: TestClient ): @@ -373,3 +200,176 @@ def test_read_system_status_no_token(client: TestClient): assert response.status_code == 401, response.text assert response.json() == {"detail": "Not authenticated"} assert response.headers["WWW-Authenticate"] == "Bearer" + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Token"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Login For Access Token", + "operationId": "login_for_access_token_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_for_access_token_token_post" + } + } + }, + "required": True, + }, + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me__get", + "security": [{"OAuth2PasswordBearer": ["me"]}], + } + }, + "/users/me/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Own Items", + "operationId": "read_own_items_users_me_items__get", + "security": [{"OAuth2PasswordBearer": ["items", "me"]}], + } + }, + "/status/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read System Status", + "operationId": "read_system_status_status__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + }, + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + "full_name": {"title": "Full Name", "type": "string"}, + "disabled": {"title": "Disabled", "type": "boolean"}, + }, + }, + "Token": { + "title": "Token", + "required": ["access_token", "token_type"], + "type": "object", + "properties": { + "access_token": {"title": "Access Token", "type": "string"}, + "token_type": {"title": "Token Type", "type": "string"}, + }, + }, + "Body_login_for_access_token_token_post": { + "title": "Body_login_for_access_token_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "password", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": {"title": "Scope", "type": "string", "default": ""}, + "client_id": {"title": "Client Id", "type": "string"}, + "client_secret": {"title": "Client Secret", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "me": "Read information about the current user.", + "items": "Read items.", + }, + "tokenUrl": "token", + } + }, + } + }, + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py index 3b0a36ebca..73cbdc538d 100644 --- a/tests/test_tutorial/test_security/test_tutorial006.py +++ b/tests/test_tutorial/test_security/test_tutorial006.py @@ -1,45 +1,14 @@ from base64 import b64encode from fastapi.testclient import TestClient -from requests.auth import HTTPBasicAuth from docs_src.security.tutorial006 import app client = TestClient(app) -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], - } - } - }, - "components": { - "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} - }, -} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - def test_security_http_basic(): - auth = HTTPBasicAuth(username="john", password="secret") - response = client.get("/users/me", auth=auth) + response = client.get("/users/me", auth=("john", "secret")) assert response.status_code == 200, response.text assert response.json() == {"username": "john", "password": "secret"} @@ -67,3 +36,30 @@ def test_security_http_basic_non_basic_credentials(): assert response.status_code == 401, response.text assert response.headers["WWW-Authenticate"] == "Basic" assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial006_an.py b/tests/test_tutorial/test_security/test_tutorial006_an.py new file mode 100644 index 0000000000..5f970ed013 --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial006_an.py @@ -0,0 +1,65 @@ +from base64 import b64encode + +from fastapi.testclient import TestClient + +from docs_src.security.tutorial006_an import app + +client = TestClient(app) + + +def test_security_http_basic(): + response = client.get("/users/me", auth=("john", "secret")) + assert response.status_code == 200, response.text + assert response.json() == {"username": "john", "password": "secret"} + + +def test_security_http_basic_no_credentials(): + response = client.get("/users/me") + assert response.json() == {"detail": "Not authenticated"} + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + + +def test_security_http_basic_invalid_credentials(): + response = client.get( + "/users/me", headers={"Authorization": "Basic notabase64token"} + ) + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_security_http_basic_non_basic_credentials(): + payload = b64encode(b"johnsecret").decode("ascii") + auth_header = f"Basic {payload}" + response = client.get("/users/me", headers={"Authorization": auth_header}) + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + assert response.json() == {"detail": "Invalid authentication credentials"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } diff --git a/tests/test_tutorial/test_security/test_tutorial006_an_py39.py b/tests/test_tutorial/test_security/test_tutorial006_an_py39.py new file mode 100644 index 0000000000..7d7a851acf --- /dev/null +++ b/tests/test_tutorial/test_security/test_tutorial006_an_py39.py @@ -0,0 +1,77 @@ +from base64 import b64encode + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.security.tutorial006_an import app + + client = TestClient(app) + return client + + +@needs_py39 +def test_security_http_basic(client: TestClient): + response = client.get("/users/me", auth=("john", "secret")) + assert response.status_code == 200, response.text + assert response.json() == {"username": "john", "password": "secret"} + + +@needs_py39 +def test_security_http_basic_no_credentials(client: TestClient): + response = client.get("/users/me") + assert response.json() == {"detail": "Not authenticated"} + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + + +@needs_py39 +def test_security_http_basic_invalid_credentials(client: TestClient): + response = client.get( + "/users/me", headers={"Authorization": "Basic notabase64token"} + ) + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + assert response.json() == {"detail": "Invalid authentication credentials"} + + +@needs_py39 +def test_security_http_basic_non_basic_credentials(client: TestClient): + payload = b64encode(b"johnsecret").decode("ascii") + auth_header = f"Basic {payload}" + response = client.get("/users/me", headers={"Authorization": auth_header}) + assert response.status_code == 401, response.text + assert response.headers["WWW-Authenticate"] == "Basic" + assert response.json() == {"detail": "Invalid authentication credentials"} + + +@needs_py39 +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases.py b/tests/test_tutorial/test_sql_databases/test_sql_databases.py index 09304ff878..a2628f3c3b 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases.py @@ -5,283 +5,6 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(scope="module") def client(tmp_path_factory: pytest.TempPathFactory): @@ -303,12 +26,6 @@ def client(tmp_path_factory: pytest.TempPathFactory): os.chdir(cwd) -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_create_user(client): test_user = {"email": "johndoe@example.com", "password": "secret"} response = client.post("/users/", json=test_user) @@ -356,12 +73,6 @@ def test_create_item(client): item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] assert item_to_check["title"] == item["title"] assert item_to_check["description"] == item["description"] - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] def test_read_items(client): @@ -372,3 +83,302 @@ def test_read_items(client): first_item = data[0] assert "title" in first_item assert "description" in first_item + + +def test_openapi_schema(client): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Users Users Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + }, + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserCreate"} + } + }, + "required": True, + }, + }, + }, + "/users/{user_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User", + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + } + }, + "/users/{user_id}/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item For User", + "operationId": "create_item_for_user_users__user_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemCreate"} + } + }, + "required": True, + }, + } + }, + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ItemCreate": { + "title": "ItemCreate", + "required": ["title"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "Item": { + "title": "Item", + "required": ["title", "id", "owner_id"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "owner_id": {"title": "Owner Id", "type": "integer"}, + }, + }, + "User": { + "title": "User", + "required": ["email", "id", "is_active"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "is_active": {"title": "Is Active", "type": "boolean"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + "default": [], + }, + }, + }, + "UserCreate": { + "title": "UserCreate", + "required": ["email", "password"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py index fbaa8938a4..02501b1a24 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py @@ -4,283 +4,6 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(scope="module") def client(): @@ -299,12 +22,6 @@ def client(): test_db.unlink() -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - def test_create_user(client): test_user = {"email": "johndoe@example.com", "password": "secret"} response = client.post("/users/", json=test_user) @@ -368,3 +85,302 @@ def test_read_items(client): first_item = data[0] assert "title" in first_item assert "description" in first_item + + +def test_openapi_schema(client): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Users Users Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + }, + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserCreate"} + } + }, + "required": True, + }, + }, + }, + "/users/{user_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User", + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + } + }, + "/users/{user_id}/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item For User", + "operationId": "create_item_for_user_users__user_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemCreate"} + } + }, + "required": True, + }, + } + }, + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ItemCreate": { + "title": "ItemCreate", + "required": ["title"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "Item": { + "title": "Item", + "required": ["title", "id", "owner_id"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "owner_id": {"title": "Owner Id", "type": "integer"}, + }, + }, + "User": { + "title": "User", + "required": ["email", "id", "is_active"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "is_active": {"title": "Is Active", "type": "boolean"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + "default": [], + }, + }, + }, + "UserCreate": { + "title": "UserCreate", + "required": ["email", "password"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py index d131b4b6a9..67b92ea4a4 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py @@ -7,283 +7,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(scope="module") def client(tmp_path_factory: pytest.TempPathFactory): @@ -306,13 +29,6 @@ def client(tmp_path_factory: pytest.TempPathFactory): os.chdir(cwd) -@needs_py310 -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_create_user(client): test_user = {"email": "johndoe@example.com", "password": "secret"} @@ -382,3 +98,303 @@ def test_read_items(client): first_item = data[0] assert "title" in first_item assert "description" in first_item + + +@needs_py310 +def test_openapi_schema(client): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Users Users Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + }, + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserCreate"} + } + }, + "required": True, + }, + }, + }, + "/users/{user_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User", + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + } + }, + "/users/{user_id}/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item For User", + "operationId": "create_item_for_user_users__user_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemCreate"} + } + }, + "required": True, + }, + } + }, + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ItemCreate": { + "title": "ItemCreate", + "required": ["title"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "Item": { + "title": "Item", + "required": ["title", "id", "owner_id"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "owner_id": {"title": "Owner Id", "type": "integer"}, + }, + }, + "User": { + "title": "User", + "required": ["email", "id", "is_active"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "is_active": {"title": "Is Active", "type": "boolean"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + "default": [], + }, + }, + }, + "UserCreate": { + "title": "UserCreate", + "required": ["email", "password"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py index 470fb52fd5..a2af20ea23 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py @@ -7,283 +7,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(scope="module") def client(tmp_path_factory: pytest.TempPathFactory): @@ -306,13 +29,6 @@ def client(tmp_path_factory: pytest.TempPathFactory): os.chdir(cwd) -@needs_py39 -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_create_user(client): test_user = {"email": "johndoe@example.com", "password": "secret"} @@ -382,3 +98,303 @@ def test_read_items(client): first_item = data[0] assert "title" in first_item assert "description" in first_item + + +@needs_py39 +def test_openapi_schema(client): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Users Users Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + }, + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserCreate"} + } + }, + "required": True, + }, + }, + }, + "/users/{user_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User", + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + } + }, + "/users/{user_id}/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item For User", + "operationId": "create_item_for_user_users__user_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemCreate"} + } + }, + "required": True, + }, + } + }, + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ItemCreate": { + "title": "ItemCreate", + "required": ["title"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "Item": { + "title": "Item", + "required": ["title", "id", "owner_id"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "owner_id": {"title": "Owner Id", "type": "integer"}, + }, + }, + "User": { + "title": "User", + "required": ["email", "id", "is_active"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "is_active": {"title": "Is Active", "type": "boolean"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + "default": [], + }, + }, + }, + "UserCreate": { + "title": "UserCreate", + "required": ["email", "password"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py index dc6a1db157..2f918cfd8b 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py @@ -7,283 +7,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py310 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(scope="module", name="client") def get_client(tmp_path_factory: pytest.TempPathFactory): @@ -305,13 +28,6 @@ def get_client(tmp_path_factory: pytest.TempPathFactory): os.chdir(cwd) -@needs_py310 -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py310 def test_create_user(client): test_user = {"email": "johndoe@example.com", "password": "secret"} @@ -381,3 +97,303 @@ def test_read_items(client): first_item = data[0] assert "title" in first_item assert "description" in first_item + + +@needs_py310 +def test_openapi_schema(client): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Users Users Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + }, + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserCreate"} + } + }, + "required": True, + }, + }, + }, + "/users/{user_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User", + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + } + }, + "/users/{user_id}/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item For User", + "operationId": "create_item_for_user_users__user_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemCreate"} + } + }, + "required": True, + }, + } + }, + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ItemCreate": { + "title": "ItemCreate", + "required": ["title"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "Item": { + "title": "Item", + "required": ["title", "id", "owner_id"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "owner_id": {"title": "Owner Id", "type": "integer"}, + }, + }, + "User": { + "title": "User", + "required": ["email", "id", "is_active"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "is_active": {"title": "Is Active", "type": "boolean"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + "default": [], + }, + }, + }, + "UserCreate": { + "title": "UserCreate", + "required": ["email", "password"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py index ebf55ed015..f2eefe41d0 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py @@ -7,283 +7,6 @@ from fastapi.testclient import TestClient from ...utils import needs_py39 -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - @pytest.fixture(scope="module", name="client") def get_client(tmp_path_factory: pytest.TempPathFactory): @@ -305,13 +28,6 @@ def get_client(tmp_path_factory: pytest.TempPathFactory): os.chdir(cwd) -@needs_py39 -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - @needs_py39 def test_create_user(client): test_user = {"email": "johndoe@example.com", "password": "secret"} @@ -381,3 +97,303 @@ def test_read_items(client): first_item = data[0] assert "title" in first_item assert "description" in first_item + + +@needs_py39 +def test_openapi_schema(client): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Users Users Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + }, + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserCreate"} + } + }, + "required": True, + }, + }, + }, + "/users/{user_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read User", + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + } + }, + "/users/{user_id}/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item For User", + "operationId": "create_item_for_user_users__user_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemCreate"} + } + }, + "required": True, + }, + } + }, + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ItemCreate": { + "title": "ItemCreate", + "required": ["title"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "Item": { + "title": "Item", + "required": ["title", "id", "owner_id"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "owner_id": {"title": "Owner Id", "type": "integer"}, + }, + }, + "User": { + "title": "User", + "required": ["email", "id", "is_active"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "is_active": {"title": "Is Active", "type": "boolean"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + "default": [], + }, + }, + }, + "UserCreate": { + "title": "UserCreate", + "required": ["email", "password"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py b/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py index d28ea5e767..d2470a2db9 100644 --- a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py +++ b/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py @@ -5,329 +5,6 @@ from unittest.mock import MagicMock import pytest from fastapi.testclient import TestClient -from ...utils import needs_py37 - -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/users/{user_id}": { - "get": { - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/{user_id}/items/": { - "post": { - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/slowusers/": { - "get": { - "summary": "Read Slow Users", - "operationId": "read_slow_users_slowusers__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Skip", "type": "integer", "default": 0}, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Limit", "type": "integer", "default": 100}, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Slow Users Slowusers Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - @pytest.fixture(scope="module") def client(): @@ -340,14 +17,6 @@ def client(): test_db.unlink() -@needs_py37 -def test_openapi_schema(client): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -@needs_py37 def test_create_user(client): test_user = {"email": "johndoe@example.com", "password": "secret"} response = client.post("/users/", json=test_user) @@ -359,7 +28,6 @@ def test_create_user(client): assert response.status_code == 400, response.text -@needs_py37 def test_get_user(client): response = client.get("/users/1") assert response.status_code == 200, response.text @@ -368,13 +36,11 @@ def test_get_user(client): assert "id" in data -@needs_py37 def test_inexistent_user(client): response = client.get("/users/999") assert response.status_code == 404, response.text -@needs_py37 def test_get_users(client): response = client.get("/users/") assert response.status_code == 200, response.text @@ -386,7 +52,6 @@ def test_get_users(client): time.sleep = MagicMock() -@needs_py37 def test_get_slowusers(client): response = client.get("/slowusers/") assert response.status_code == 200, response.text @@ -395,7 +60,6 @@ def test_get_slowusers(client): assert "id" in data[0] -@needs_py37 def test_create_item(client): item = {"title": "Foo", "description": "Something that fights"} response = client.post("/users/1/items/", json=item) @@ -419,7 +83,6 @@ def test_create_item(client): assert item_to_check["description"] == item["description"] -@needs_py37 def test_read_items(client): response = client.get("/items/") assert response.status_code == 200, response.text @@ -428,3 +91,354 @@ def test_read_items(client): first_item = data[0] assert "title" in first_item assert "description" in first_item + + +def test_openapi_schema(client): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Users Users Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/UserCreate"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/users/{user_id}": { + "get": { + "summary": "Read User", + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/{user_id}/items/": { + "post": { + "summary": "Create Item For User", + "operationId": "create_item_for_user_users__user_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemCreate"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/slowusers/": { + "get": { + "summary": "Read Slow Users", + "operationId": "read_slow_users_slowusers__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Slow Users Slowusers Get", + "type": "array", + "items": {"$ref": "#/components/schemas/User"}, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["title", "id", "owner_id"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "owner_id": {"title": "Owner Id", "type": "integer"}, + }, + }, + "ItemCreate": { + "title": "ItemCreate", + "required": ["title"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + }, + "User": { + "title": "User", + "required": ["email", "id", "is_active"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "is_active": {"title": "Is Active", "type": "boolean"}, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + "default": [], + }, + }, + }, + "UserCreate": { + "title": "UserCreate", + "required": ["email", "password"], + "type": "object", + "properties": { + "email": {"title": "Email", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_testing/test_main.py b/tests/test_tutorial/test_testing/test_main.py index e6747fffd0..937ce75e42 100644 --- a/tests/test_tutorial/test_testing/test_main.py +++ b/tests/test_tutorial/test_testing/test_main.py @@ -1,30 +1,28 @@ from docs_src.app_testing.test_main import client, test_read_main -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Main", - "operationId": "read_main__get", - } - } - }, -} + +def test_main(): + test_read_main() def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_main(): - test_read_main() + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Main", + "operationId": "read_main__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_testing/test_main_b_an.py b/tests/test_tutorial/test_testing/test_main_b_an.py new file mode 100644 index 0000000000..b64c5f7107 --- /dev/null +++ b/tests/test_tutorial/test_testing/test_main_b_an.py @@ -0,0 +1,10 @@ +from docs_src.app_testing.app_b_an import test_main + + +def test_app(): + test_main.test_create_existing_item() + test_main.test_create_item() + test_main.test_create_item_bad_token() + test_main.test_read_inexistent_item() + test_main.test_read_item() + test_main.test_read_item_bad_token() diff --git a/tests/test_tutorial/test_testing/test_main_b_an_py310.py b/tests/test_tutorial/test_testing/test_main_b_an_py310.py new file mode 100644 index 0000000000..194700b6dd --- /dev/null +++ b/tests/test_tutorial/test_testing/test_main_b_an_py310.py @@ -0,0 +1,13 @@ +from ...utils import needs_py310 + + +@needs_py310 +def test_app(): + from docs_src.app_testing.app_b_an_py310 import test_main + + test_main.test_create_existing_item() + test_main.test_create_item() + test_main.test_create_item_bad_token() + test_main.test_read_inexistent_item() + test_main.test_read_item() + test_main.test_read_item_bad_token() diff --git a/tests/test_tutorial/test_testing/test_main_b_an_py39.py b/tests/test_tutorial/test_testing/test_main_b_an_py39.py new file mode 100644 index 0000000000..2f8a13623f --- /dev/null +++ b/tests/test_tutorial/test_testing/test_main_b_an_py39.py @@ -0,0 +1,13 @@ +from ...utils import needs_py39 + + +@needs_py39 +def test_app(): + from docs_src.app_testing.app_b_an_py39 import test_main + + test_main.test_create_existing_item() + test_main.test_create_item() + test_main.test_create_item_bad_token() + test_main.test_read_inexistent_item() + test_main.test_read_item() + test_main.test_read_item_bad_token() diff --git a/tests/test_tutorial/test_testing/test_tutorial001.py b/tests/test_tutorial/test_testing/test_tutorial001.py index 7dea477f0e..f3db70af2b 100644 --- a/tests/test_tutorial/test_testing/test_tutorial001.py +++ b/tests/test_tutorial/test_testing/test_tutorial001.py @@ -1,30 +1,28 @@ from docs_src.app_testing.tutorial001 import client, test_read_main -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Main", - "operationId": "read_main__get", - } - } - }, -} + +def test_main(): + test_read_main() def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -def test_main(): - test_read_main() + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Main", + "operationId": "read_main__get", + } + } + }, + } diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an.py new file mode 100644 index 0000000000..fc1f9149a6 --- /dev/null +++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an.py @@ -0,0 +1,56 @@ +from docs_src.dependency_testing.tutorial001_an import ( + app, + client, + test_override_in_items, + test_override_in_items_with_params, + test_override_in_items_with_q, +) + + +def test_override_in_items_run(): + test_override_in_items() + + +def test_override_in_items_with_q_run(): + test_override_in_items_with_q() + + +def test_override_in_items_with_params_run(): + test_override_in_items_with_params() + + +def test_override_in_users(): + response = client.get("/users/") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": None, "skip": 5, "limit": 10}, + } + + +def test_override_in_users_with_q(): + response = client.get("/users/?q=foo") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +def test_override_in_users_with_params(): + response = client.get("/users/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +def test_normal_app(): + app.dependency_overrides = None + response = client.get("/items/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Items!", + "params": {"q": "foo", "skip": 100, "limit": 200}, + } diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py310.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py310.py new file mode 100644 index 0000000000..a3d27f47f4 --- /dev/null +++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py310.py @@ -0,0 +1,75 @@ +from ...utils import needs_py310 + + +@needs_py310 +def test_override_in_items_run(): + from docs_src.dependency_testing.tutorial001_an_py310 import test_override_in_items + + test_override_in_items() + + +@needs_py310 +def test_override_in_items_with_q_run(): + from docs_src.dependency_testing.tutorial001_an_py310 import ( + test_override_in_items_with_q, + ) + + test_override_in_items_with_q() + + +@needs_py310 +def test_override_in_items_with_params_run(): + from docs_src.dependency_testing.tutorial001_an_py310 import ( + test_override_in_items_with_params, + ) + + test_override_in_items_with_params() + + +@needs_py310 +def test_override_in_users(): + from docs_src.dependency_testing.tutorial001_an_py310 import client + + response = client.get("/users/") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": None, "skip": 5, "limit": 10}, + } + + +@needs_py310 +def test_override_in_users_with_q(): + from docs_src.dependency_testing.tutorial001_an_py310 import client + + response = client.get("/users/?q=foo") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +@needs_py310 +def test_override_in_users_with_params(): + from docs_src.dependency_testing.tutorial001_an_py310 import client + + response = client.get("/users/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +@needs_py310 +def test_normal_app(): + from docs_src.dependency_testing.tutorial001_an_py310 import app, client + + app.dependency_overrides = None + response = client.get("/items/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Items!", + "params": {"q": "foo", "skip": 100, "limit": 200}, + } diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py39.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py39.py new file mode 100644 index 0000000000..f03ed5e07a --- /dev/null +++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py39.py @@ -0,0 +1,75 @@ +from ...utils import needs_py39 + + +@needs_py39 +def test_override_in_items_run(): + from docs_src.dependency_testing.tutorial001_an_py39 import test_override_in_items + + test_override_in_items() + + +@needs_py39 +def test_override_in_items_with_q_run(): + from docs_src.dependency_testing.tutorial001_an_py39 import ( + test_override_in_items_with_q, + ) + + test_override_in_items_with_q() + + +@needs_py39 +def test_override_in_items_with_params_run(): + from docs_src.dependency_testing.tutorial001_an_py39 import ( + test_override_in_items_with_params, + ) + + test_override_in_items_with_params() + + +@needs_py39 +def test_override_in_users(): + from docs_src.dependency_testing.tutorial001_an_py39 import client + + response = client.get("/users/") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": None, "skip": 5, "limit": 10}, + } + + +@needs_py39 +def test_override_in_users_with_q(): + from docs_src.dependency_testing.tutorial001_an_py39 import client + + response = client.get("/users/?q=foo") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +@needs_py39 +def test_override_in_users_with_params(): + from docs_src.dependency_testing.tutorial001_an_py39 import client + + response = client.get("/users/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +@needs_py39 +def test_normal_app(): + from docs_src.dependency_testing.tutorial001_an_py39 import app, client + + app.dependency_overrides = None + response = client.get("/items/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Items!", + "params": {"q": "foo", "skip": 100, "limit": 200}, + } diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_py310.py new file mode 100644 index 0000000000..776b916ff0 --- /dev/null +++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_py310.py @@ -0,0 +1,75 @@ +from ...utils import needs_py310 + + +@needs_py310 +def test_override_in_items_run(): + from docs_src.dependency_testing.tutorial001_py310 import test_override_in_items + + test_override_in_items() + + +@needs_py310 +def test_override_in_items_with_q_run(): + from docs_src.dependency_testing.tutorial001_py310 import ( + test_override_in_items_with_q, + ) + + test_override_in_items_with_q() + + +@needs_py310 +def test_override_in_items_with_params_run(): + from docs_src.dependency_testing.tutorial001_py310 import ( + test_override_in_items_with_params, + ) + + test_override_in_items_with_params() + + +@needs_py310 +def test_override_in_users(): + from docs_src.dependency_testing.tutorial001_py310 import client + + response = client.get("/users/") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": None, "skip": 5, "limit": 10}, + } + + +@needs_py310 +def test_override_in_users_with_q(): + from docs_src.dependency_testing.tutorial001_py310 import client + + response = client.get("/users/?q=foo") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +@needs_py310 +def test_override_in_users_with_params(): + from docs_src.dependency_testing.tutorial001_py310 import client + + response = client.get("/users/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Users!", + "params": {"q": "foo", "skip": 5, "limit": 10}, + } + + +@needs_py310 +def test_normal_app(): + from docs_src.dependency_testing.tutorial001_py310 import app, client + + app.dependency_overrides = None + response = client.get("/items/?q=foo&skip=100&limit=200") + assert response.status_code == 200, response.text + assert response.json() == { + "message": "Hello Items!", + "params": {"q": "foo", "skip": 100, "limit": 200}, + } diff --git a/tests/test_tutorial/test_websockets/test_tutorial002.py b/tests/test_tutorial/test_websockets/test_tutorial002.py index a8523c9c4f..bb5ccbf8ef 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial002.py +++ b/tests/test_tutorial/test_websockets/test_tutorial002.py @@ -4,20 +4,18 @@ from fastapi.websockets import WebSocketDisconnect from docs_src.websockets.tutorial002 import app -client = TestClient(app) - def test_main(): + client = TestClient(app) response = client.get("/") assert response.status_code == 200, response.text assert b"" in response.content def test_websocket_with_cookie(): + client = TestClient(app, cookies={"session": "fakesession"}) with pytest.raises(WebSocketDisconnect): - with client.websocket_connect( - "/items/foo/ws", cookies={"session": "fakesession"} - ) as websocket: + with client.websocket_connect("/items/foo/ws") as websocket: message = "Message one" websocket.send_text(message) data = websocket.receive_text() @@ -33,6 +31,7 @@ def test_websocket_with_cookie(): def test_websocket_with_header(): + client = TestClient(app) with pytest.raises(WebSocketDisconnect): with client.websocket_connect("/items/bar/ws?token=some-token") as websocket: message = "Message one" @@ -50,6 +49,7 @@ def test_websocket_with_header(): def test_websocket_with_header_and_query(): + client = TestClient(app) with pytest.raises(WebSocketDisconnect): with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket: message = "Message one" @@ -71,6 +71,7 @@ def test_websocket_with_header_and_query(): def test_websocket_no_credentials(): + client = TestClient(app) with pytest.raises(WebSocketDisconnect): with client.websocket_connect("/items/foo/ws"): pytest.fail( @@ -79,6 +80,7 @@ def test_websocket_no_credentials(): def test_websocket_invalid_data(): + client = TestClient(app) with pytest.raises(WebSocketDisconnect): with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"): pytest.fail( diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_an.py b/tests/test_tutorial/test_websockets/test_tutorial002_an.py new file mode 100644 index 0000000000..ec78d70d30 --- /dev/null +++ b/tests/test_tutorial/test_websockets/test_tutorial002_an.py @@ -0,0 +1,88 @@ +import pytest +from fastapi.testclient import TestClient +from fastapi.websockets import WebSocketDisconnect + +from docs_src.websockets.tutorial002_an import app + + +def test_main(): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"" in response.content + + +def test_websocket_with_cookie(): + client = TestClient(app, cookies={"session": "fakesession"}) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + + +def test_websocket_with_header(): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/bar/ws?token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + + +def test_websocket_with_header_and_query(): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + + +def test_websocket_no_credentials(): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover + + +def test_websocket_invalid_data(): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_an_py310.py b/tests/test_tutorial/test_websockets/test_tutorial002_an_py310.py new file mode 100644 index 0000000000..23b4bcb788 --- /dev/null +++ b/tests/test_tutorial/test_websockets/test_tutorial002_an_py310.py @@ -0,0 +1,102 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from fastapi.websockets import WebSocketDisconnect + +from ...utils import needs_py310 + + +@pytest.fixture(name="app") +def get_app(): + from docs_src.websockets.tutorial002_an_py310 import app + + return app + + +@needs_py310 +def test_main(app: FastAPI): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"" in response.content + + +@needs_py310 +def test_websocket_with_cookie(app: FastAPI): + client = TestClient(app, cookies={"session": "fakesession"}) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + + +@needs_py310 +def test_websocket_with_header(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/bar/ws?token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + + +@needs_py310 +def test_websocket_with_header_and_query(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + + +@needs_py310 +def test_websocket_no_credentials(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover + + +@needs_py310 +def test_websocket_invalid_data(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_an_py39.py b/tests/test_tutorial/test_websockets/test_tutorial002_an_py39.py new file mode 100644 index 0000000000..2d77f05b38 --- /dev/null +++ b/tests/test_tutorial/test_websockets/test_tutorial002_an_py39.py @@ -0,0 +1,102 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from fastapi.websockets import WebSocketDisconnect + +from ...utils import needs_py39 + + +@pytest.fixture(name="app") +def get_app(): + from docs_src.websockets.tutorial002_an_py39 import app + + return app + + +@needs_py39 +def test_main(app: FastAPI): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"" in response.content + + +@needs_py39 +def test_websocket_with_cookie(app: FastAPI): + client = TestClient(app, cookies={"session": "fakesession"}) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + + +@needs_py39 +def test_websocket_with_header(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/bar/ws?token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + + +@needs_py39 +def test_websocket_with_header_and_query(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + + +@needs_py39 +def test_websocket_no_credentials(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover + + +@needs_py39 +def test_websocket_invalid_data(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_py310.py b/tests/test_tutorial/test_websockets/test_tutorial002_py310.py new file mode 100644 index 0000000000..03bc27bdfd --- /dev/null +++ b/tests/test_tutorial/test_websockets/test_tutorial002_py310.py @@ -0,0 +1,102 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from fastapi.websockets import WebSocketDisconnect + +from ...utils import needs_py310 + + +@pytest.fixture(name="app") +def get_app(): + from docs_src.websockets.tutorial002_py310 import app + + return app + + +@needs_py310 +def test_main(app: FastAPI): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"" in response.content + + +@needs_py310 +def test_websocket_with_cookie(app: FastAPI): + client = TestClient(app, cookies={"session": "fakesession"}) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: fakesession" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: foo" + + +@needs_py310 +def test_websocket_with_header(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/bar/ws?token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: bar" + + +@needs_py310 +def test_websocket_with_header_and_query(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket: + message = "Message one" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + message = "Message two" + websocket.send_text(message) + data = websocket.receive_text() + assert data == "Session cookie or query token value is: some-token" + data = websocket.receive_text() + assert data == "Query parameter q is: 3" + data = websocket.receive_text() + assert data == f"Message text was: {message}, for item ID: 2" + + +@needs_py310 +def test_websocket_no_credentials(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover + + +@needs_py310 +def test_websocket_invalid_data(app: FastAPI): + client = TestClient(app) + with pytest.raises(WebSocketDisconnect): + with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"): + pytest.fail( + "did not raise WebSocketDisconnect on __enter__" + ) # pragma: no cover diff --git a/tests/test_tutorial/test_websockets/test_tutorial003_py39.py b/tests/test_tutorial/test_websockets/test_tutorial003_py39.py new file mode 100644 index 0000000000..06c4a92796 --- /dev/null +++ b/tests/test_tutorial/test_websockets/test_tutorial003_py39.py @@ -0,0 +1,50 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + + +@pytest.fixture(name="app") +def get_app(): + from docs_src.websockets.tutorial003_py39 import app + + return app + + +@pytest.fixture(name="html") +def get_html(): + from docs_src.websockets.tutorial003_py39 import html + + return html + + +@pytest.fixture(name="client") +def get_client(app: FastAPI): + client = TestClient(app) + + return client + + +@needs_py39 +def test_get(client: TestClient, html: str): + response = client.get("/") + assert response.text == html + + +@needs_py39 +def test_websocket_handle_disconnection(client: TestClient): + with client.websocket_connect("/ws/1234") as connection, client.websocket_connect( + "/ws/5678" + ) as connection_two: + connection.send_text("Hello from 1234") + data1 = connection.receive_text() + assert data1 == "You wrote: Hello from 1234" + data2 = connection_two.receive_text() + client1_says = "Client #1234 says: Hello from 1234" + assert data2 == client1_says + data1 = connection.receive_text() + assert data1 == client1_says + connection_two.close() + data1 = connection.receive_text() + assert data1 == "Client #5678 left the chat" diff --git a/tests/test_typing_python39.py b/tests/test_typing_python39.py index b1ea635e77..26775efd48 100644 --- a/tests/test_typing_python39.py +++ b/tests/test_typing_python39.py @@ -1,10 +1,10 @@ from fastapi import FastAPI from fastapi.testclient import TestClient -from .utils import needs_py39 +from .utils import needs_py310 -@needs_py39 +@needs_py310 def test_typing(): types = { list[int]: [1, 2, 3], diff --git a/tests/test_union_body.py b/tests/test_union_body.py index 3e424de07d..bc1e744328 100644 --- a/tests/test_union_body.py +++ b/tests/test_union_body.py @@ -22,95 +22,6 @@ def save_union_body(item: Union[OtherItem, Item]): client = TestClient(app) -item_openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Save Union Body", - "operationId": "save_union_body_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Item", - "anyOf": [ - {"$ref": "#/components/schemas/OtherItem"}, - {"$ref": "#/components/schemas/Item"}, - ], - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "OtherItem": { - "title": "OtherItem", - "required": ["price"], - "type": "object", - "properties": {"price": {"title": "Price", "type": "integer"}}, - }, - "Item": { - "title": "Item", - "type": "object", - "properties": {"name": {"title": "Name", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -def test_item_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == item_openapi_schema - def test_post_other_item(): response = client.post("/items/", json={"price": 100}) @@ -122,3 +33,92 @@ def test_post_item(): response = client.post("/items/", json={"name": "Foo"}) assert response.status_code == 200, response.text assert response.json() == {"item": {"name": "Foo"}} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Save Union Body", + "operationId": "save_union_body_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Item", + "anyOf": [ + {"$ref": "#/components/schemas/OtherItem"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "OtherItem": { + "title": "OtherItem", + "required": ["price"], + "type": "object", + "properties": {"price": {"title": "Price", "type": "integer"}}, + }, + "Item": { + "title": "Item", + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py index 60b327ebcc..988b920aa7 100644 --- a/tests/test_union_inherited_body.py +++ b/tests/test_union_inherited_body.py @@ -4,14 +4,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel -from .utils import needs_py37 - -# In Python 3.6: -# u = Union[ExtendedItem, Item] == __main__.Item - -# But in Python 3.7: -# u = Union[ExtendedItem, Item] == typing.Union[__main__.ExtendedItem, __main__.Item] - app = FastAPI() @@ -31,109 +23,105 @@ def save_union_different_body(item: Union[ExtendedItem, Item]): client = TestClient(app) -inherited_item_openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Save Union Different Body", - "operationId": "save_union_different_body_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Item", - "anyOf": [ - {"$ref": "#/components/schemas/ExtendedItem"}, - {"$ref": "#/components/schemas/Item"}, - ], - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": {"name": {"title": "Name", "type": "string"}}, - }, - "ExtendedItem": { - "title": "ExtendedItem", - "required": ["age"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, -} - - -@needs_py37 -def test_inherited_item_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == inherited_item_openapi_schema - - -@needs_py37 def test_post_extended_item(): response = client.post("/items/", json={"name": "Foo", "age": 5}) assert response.status_code == 200, response.text assert response.json() == {"item": {"name": "Foo", "age": 5}} -@needs_py37 def test_post_item(): response = client.post("/items/", json={"name": "Foo"}) assert response.status_code == 200, response.text assert response.json() == {"item": {"name": "Foo"}} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Save Union Different Body", + "operationId": "save_union_different_body_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Item", + "anyOf": [ + {"$ref": "#/components/schemas/ExtendedItem"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + }, + "ExtendedItem": { + "title": "ExtendedItem", + "required": ["age"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + } + }, + } diff --git a/tests/test_validate_response.py b/tests/test_validate_response.py index 45d303e20e..62f51c960d 100644 --- a/tests/test_validate_response.py +++ b/tests/test_validate_response.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Union import pytest from fastapi import FastAPI @@ -19,6 +19,19 @@ def get_invalid(): return {"name": "invalid", "price": "foo"} +@app.get("/items/invalidnone", response_model=Item) +def get_invalid_none(): + return None + + +@app.get("/items/validnone", response_model=Union[Item, None]) +def get_valid_none(send_none: bool = False): + if send_none: + return None + else: + return {"name": "invalid", "price": 3.2} + + @app.get("/items/innerinvalid", response_model=Item) def get_innerinvalid(): return {"name": "double invalid", "price": "foo", "owner_ids": ["foo", "bar"]} @@ -41,6 +54,25 @@ def test_invalid(): client.get("/items/invalid") +def test_invalid_none(): + with pytest.raises(ValidationError): + client.get("/items/invalidnone") + + +def test_valid_none_data(): + response = client.get("/items/validnone") + data = response.json() + assert response.status_code == 200 + assert data == {"name": "invalid", "price": 3.2, "owner_ids": None} + + +def test_valid_none_none(): + response = client.get("/items/validnone", params={"send_none": "true"}) + data = response.json() + assert response.status_code == 200 + assert data is None + + def test_double_invalid(): with pytest.raises(ValidationError): client.get("/items/innerinvalid") diff --git a/tests/test_ws_dependencies.py b/tests/test_ws_dependencies.py new file mode 100644 index 0000000000..ccb1c4b7da --- /dev/null +++ b/tests/test_ws_dependencies.py @@ -0,0 +1,73 @@ +import json +from typing import List + +from fastapi import APIRouter, Depends, FastAPI, WebSocket +from fastapi.testclient import TestClient +from typing_extensions import Annotated + + +def dependency_list() -> List[str]: + return [] + + +DepList = Annotated[List[str], Depends(dependency_list)] + + +def create_dependency(name: str): + def fun(deps: DepList): + deps.append(name) + + return Depends(fun) + + +router = APIRouter(dependencies=[create_dependency("router")]) +prefix_router = APIRouter(dependencies=[create_dependency("prefix_router")]) +app = FastAPI(dependencies=[create_dependency("app")]) + + +@app.websocket("/", dependencies=[create_dependency("index")]) +async def index(websocket: WebSocket, deps: DepList): + await websocket.accept() + await websocket.send_text(json.dumps(deps)) + await websocket.close() + + +@router.websocket("/router", dependencies=[create_dependency("routerindex")]) +async def routerindex(websocket: WebSocket, deps: DepList): + await websocket.accept() + await websocket.send_text(json.dumps(deps)) + await websocket.close() + + +@prefix_router.websocket("/", dependencies=[create_dependency("routerprefixindex")]) +async def routerprefixindex(websocket: WebSocket, deps: DepList): + await websocket.accept() + await websocket.send_text(json.dumps(deps)) + await websocket.close() + + +app.include_router(router, dependencies=[create_dependency("router2")]) +app.include_router( + prefix_router, prefix="/prefix", dependencies=[create_dependency("prefix_router2")] +) + + +def test_index(): + client = TestClient(app) + with client.websocket_connect("/") as websocket: + data = json.loads(websocket.receive_text()) + assert data == ["app", "index"] + + +def test_routerindex(): + client = TestClient(app) + with client.websocket_connect("/router") as websocket: + data = json.loads(websocket.receive_text()) + assert data == ["app", "router2", "router", "routerindex"] + + +def test_routerprefixindex(): + client = TestClient(app) + with client.websocket_connect("/prefix/") as websocket: + data = json.loads(websocket.receive_text()) + assert data == ["app", "prefix_router2", "prefix_router", "routerprefixindex"] diff --git a/tests/test_ws_router.py b/tests/test_ws_router.py index fbca104a23..240a42bb0c 100644 --- a/tests/test_ws_router.py +++ b/tests/test_ws_router.py @@ -1,4 +1,16 @@ -from fastapi import APIRouter, Depends, FastAPI, WebSocket +import functools + +import pytest +from fastapi import ( + APIRouter, + Depends, + FastAPI, + Header, + WebSocket, + WebSocketDisconnect, + status, +) +from fastapi.middleware import Middleware from fastapi.testclient import TestClient router = APIRouter() @@ -35,6 +47,14 @@ async def routerindex2(websocket: WebSocket): await websocket.close() +@router.websocket("/router/{pathparam:path}") +async def routerindexparams(websocket: WebSocket, pathparam: str, queryparam: str): + await websocket.accept() + await websocket.send_text(pathparam) + await websocket.send_text(queryparam) + await websocket.close() + + async def ws_dependency(): return "Socket Dependency" @@ -55,9 +75,44 @@ async def router_native_prefix_ws(websocket: WebSocket): await websocket.close() -app.include_router(router) -app.include_router(prefix_router, prefix="/prefix") -app.include_router(native_prefix_route) +async def ws_dependency_err(): + raise NotImplementedError() + + +@router.websocket("/depends-err/") +async def router_ws_depends_err(websocket: WebSocket, data=Depends(ws_dependency_err)): + pass # pragma: no cover + + +async def ws_dependency_validate(x_missing: str = Header()): + pass # pragma: no cover + + +@router.websocket("/depends-validate/") +async def router_ws_depends_validate( + websocket: WebSocket, data=Depends(ws_dependency_validate) +): + pass # pragma: no cover + + +class CustomError(Exception): + pass + + +@router.websocket("/custom_error/") +async def router_ws_custom_error(websocket: WebSocket): + raise CustomError() + + +def make_app(app=None, **kwargs): + app = app or FastAPI(**kwargs) + app.include_router(router) + app.include_router(prefix_router, prefix="/prefix") + app.include_router(native_prefix_route) + return app + + +app = make_app(app) def test_app(): @@ -103,6 +158,114 @@ def test_router_ws_depends(): def test_router_ws_depends_with_override(): client = TestClient(app) - app.dependency_overrides[ws_dependency] = lambda: "Override" + app.dependency_overrides[ws_dependency] = lambda: "Override" # noqa: E731 with client.websocket_connect("/router-ws-depends/") as websocket: assert websocket.receive_text() == "Override" + + +def test_router_with_params(): + client = TestClient(app) + with client.websocket_connect( + "/router/path/to/file?queryparam=a_query_param" + ) as websocket: + data = websocket.receive_text() + assert data == "path/to/file" + data = websocket.receive_text() + assert data == "a_query_param" + + +def test_wrong_uri(): + """ + Verify that a websocket connection to a non-existent endpoing returns in a shutdown + """ + client = TestClient(app) + with pytest.raises(WebSocketDisconnect) as e: + with client.websocket_connect("/no-router/"): + pass # pragma: no cover + assert e.value.code == status.WS_1000_NORMAL_CLOSURE + + +def websocket_middleware(middleware_func): + """ + Helper to create a Starlette pure websocket middleware + """ + + def middleware_constructor(app): + @functools.wraps(app) + async def wrapped_app(scope, receive, send): + if scope["type"] != "websocket": + return await app(scope, receive, send) # pragma: no cover + + async def call_next(): + return await app(scope, receive, send) + + websocket = WebSocket(scope, receive=receive, send=send) + return await middleware_func(websocket, call_next) + + return wrapped_app + + return middleware_constructor + + +def test_depend_validation(): + """ + Verify that a validation in a dependency invokes the correct exception handler + """ + caught = [] + + @websocket_middleware + async def catcher(websocket, call_next): + try: + return await call_next() + except Exception as e: # pragma: no cover + caught.append(e) + raise + + myapp = make_app(middleware=[Middleware(catcher)]) + + client = TestClient(myapp) + with pytest.raises(WebSocketDisconnect) as e: + with client.websocket_connect("/depends-validate/"): + pass # pragma: no cover + # the validation error does produce a close message + assert e.value.code == status.WS_1008_POLICY_VIOLATION + # and no error is leaked + assert caught == [] + + +def test_depend_err_middleware(): + """ + Verify that it is possible to write custom WebSocket middleware to catch errors + """ + + @websocket_middleware + async def errorhandler(websocket: WebSocket, call_next): + try: + return await call_next() + except Exception as e: + await websocket.close(code=status.WS_1006_ABNORMAL_CLOSURE, reason=repr(e)) + + myapp = make_app(middleware=[Middleware(errorhandler)]) + client = TestClient(myapp) + with pytest.raises(WebSocketDisconnect) as e: + with client.websocket_connect("/depends-err/"): + pass # pragma: no cover + assert e.value.code == status.WS_1006_ABNORMAL_CLOSURE + assert "NotImplementedError" in e.value.reason + + +def test_depend_err_handler(): + """ + Verify that it is possible to write custom WebSocket middleware to catch errors + """ + + async def custom_handler(websocket: WebSocket, exc: CustomError) -> None: + await websocket.close(1002, "foo") + + myapp = make_app(exception_handlers={CustomError: custom_handler}) + client = TestClient(myapp) + with pytest.raises(WebSocketDisconnect) as e: + with client.websocket_connect("/custom_error/"): + pass # pragma: no cover + assert e.value.code == 1002 + assert "foo" in e.value.reason diff --git a/tests/utils.py b/tests/utils.py index 777bfe81da..5305424c45 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,6 @@ import sys import pytest -needs_py37 = pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7+") needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9+") needs_py310 = pytest.mark.skipif( sys.version_info < (3, 10), reason="requires python3.10+"